Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/linejs/client/features/call/planet/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,25 @@ export function packPlanetScMsgKaReq(inner: Uint8Array): Uint8Array {
return finalize(b);
}

// ─── cc_upd_rsp (mid-call quality report / keepalive) ───────────────────

/**
* Pack a flat-format UPD_RSP message. The relay sends a flood of cc
* messages with `{ 1:16, 2:<seq>, 3:300, 4:<blob> }` after `conn_req`.
* linejs must echo back an equivalent to avoid T103.
*
* The flat format bypasses the normal cc_msg oneof wrapper — it is NOT
* `wrapCcMsg(CC_MSG.UPD_RSP, ...)`, but a top-level protobuf placed
* directly in field 3 (cc) of the outer planet_msg.
*/
export function packCcUpdRsp(seq: bigint, intervalMs = 300): Uint8Array {
const b: Buf = { bytes: [] };
emitUint32(b, 1, CC_MSG.UPD_RSP);
emitUint64(b, 2, seq);
emitUint32(b, 3, intervalMs);
return finalize(b);
}

// ─── handshake helper — extract rmt_nonce from incoming msg ─────────────

/** Extract the loc_nonce (field 6) from a decrypted incoming planet_msg.
Expand Down Expand Up @@ -1348,6 +1367,8 @@ export interface DecodedPlanetCcMsg {
bodyTag?: number;
bodyName?: string;
bodyBytes?: Uint8Array;
/** Raw decoded fields for non-standard (flat) cc messages. */
rawFields?: DecodedField[];
}

export interface DecodedPlanetMcMsg {
Expand Down Expand Up @@ -1387,6 +1408,15 @@ export function decodePlanetCcMsg(bytes: Uint8Array): DecodedPlanetCcMsg {
decoded.bodyBytes = oneof.value;
}
}
if (!decoded.bodyTag && !hdrBytes && !body && fields.length > 0) {
const typeVal = asNumberField(fields, 1);
if (typeVal !== undefined) {
decoded.bodyTag = typeVal;
decoded.bodyName = CC_MSG_NAMES[typeVal] ??
`cc_flat_${typeVal}`;
decoded.rawFields = fields;
}
}
return decoded;
}

Expand Down
52 changes: 52 additions & 0 deletions packages/linejs/client/features/call/planet/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
packCcParticipateReq,
packCcRelReq,
packCcSetupReq,
packCcUpdRsp,
packKeepaliveReq,
packMcDataReq,
packMcDataRsp,
Expand Down Expand Up @@ -1274,6 +1275,9 @@ export class PlanetTransport implements CallTransport {
},
).catch(() => {});
}
if (msg.cc?.rawFields && msg.cc.bodyTag === CC_MSG.UPD_RSP) {
void this.#handleFlatCcUpd(msg.cc.rawFields).catch(() => {});
}
} catch {
// Keep the raw reply flowing even if a newer message type is unknown.
}
Expand Down Expand Up @@ -2308,6 +2312,53 @@ export class PlanetTransport implements CallTransport {
);
}

async #handleFlatCcUpd(rawFields: DecodedField[]): Promise<void> {
const seq = rawFields.find((f) => f.tag === 2)?.value;
const seqBig = typeof seq === "bigint" ? seq : 0n;
const interval = fieldNumber(rawFields, 3) ?? 300;
if (!this.#updRspActive) {
this.#updRspActive = true;
this.#updRspIntervalMs = interval;
this.#startCcUpdTimer();
}
this.#lastUpdSeq = seqBig;
}

#updRspActive = false;
#updRspIntervalMs = 300;
#lastUpdSeq = 0n;
#ccUpdTimer: ReturnType<typeof setTimeout> | undefined;
#ccUpdSeq = 0n;

#startCcUpdTimer() {
this.#clearCcUpdTimer();
const tick = () => {
if (this.#closed) return;
void this.#sendCcUpdRsp().catch(() => {}).finally(() => {
if (!this.#closed && this.#updRspActive) {
this.#ccUpdTimer = setTimeout(tick, this.#updRspIntervalMs);
}
});
};
this.#ccUpdTimer = setTimeout(tick, this.#updRspIntervalMs);
}

#clearCcUpdTimer() {
if (this.#ccUpdTimer !== undefined) {
clearTimeout(this.#ccUpdTimer);
this.#ccUpdTimer = undefined;
}
}

async #sendCcUpdRsp(): Promise<void> {
this.#ccUpdSeq++;
const updBytes = packCcUpdRsp(this.#ccUpdSeq, this.#updRspIntervalMs);
await this.#sendEnvelope(
{ kind: "cc", data: updBytes },
{ msgId: ccMsgId(CC_MSG.UPD_RSP) },
);
}

#clearKeepalive() {
if (this.#keepaliveTimer !== undefined) {
clearTimeout(this.#keepaliveTimer);
Expand Down Expand Up @@ -2347,6 +2398,7 @@ export class PlanetTransport implements CallTransport {
async close(): Promise<void> {
this.#closed = true;
this.#clearKeepalive();
this.#clearCcUpdTimer();
try {
if (
this.#setupSent && this.#route && (this.#sock || this.#opts.wireSend)
Expand Down
Loading