Skip to content
Merged
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
43 changes: 43 additions & 0 deletions packages/linejs/client/features/call/planet/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,49 @@ export function packMcDataSessionPayload(body: Uint8Array): Uint8Array {
return concatSchemaBytes([header, body, new Uint8Array(pad)]);
}

// ─── mc join/change responses (1:1 BEPI handshake) ─────────────────────

export interface McSessionRsp {
result?: number;
relCode?: number;
relPhrase?: string;
data?: Uint8Array;
}

export function packMcJoinRsp(r: McSessionRsp): Uint8Array {
const b: Buf = { bytes: [] };
if (r.result !== undefined) emitUint32(b, 1, r.result);
if (r.relCode !== undefined) emitUint32(b, 2, r.relCode);
if (r.relPhrase !== undefined) emitString(b, 3, r.relPhrase);
if (r.data) emitMessage(b, 17, r.data);
return finalize(b);
}

export function packMcChangeRsp(r: McSessionRsp): Uint8Array {
const b: Buf = { bytes: [] };
if (r.result !== undefined) emitUint32(b, 1, r.result);
if (r.relCode !== undefined) emitUint32(b, 2, r.relCode);
if (r.relPhrase !== undefined) emitString(b, 3, r.relPhrase);
if (r.data) emitMessage(b, 17, r.data);
return finalize(b);
}

export function packMcCheckRpt(strmSpec: Uint8Array): Uint8Array {
const b: Buf = { bytes: [] };
emitMessage(b, 17, strmSpec);
return finalize(b);
}

export function packBepiChannelOpen(token: bigint): Uint8Array {
const inner: Buf = { bytes: [] };
emitSfixed64(inner, 1, token);
const mid: Buf = { bytes: [] };
emitMessage(mid, 3, finalize(inner));
const outer: Buf = { bytes: [] };
emitMessage(outer, 2, finalize(mid));
return finalize(outer);
}

// ─── cc_msg oneof wrapper ───────────────────────────────────────────────

export function wrapCcMsg(
Expand Down
137 changes: 133 additions & 4 deletions packages/linejs/client/features/call/planet/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,20 @@ import {
extractRmtNonceFromReply,
MC_MSG,
type NativeSetupOffer,
packBepiChannelOpen,
packCcConnRsp,
packCcInfoReq,
packCcInfoRsp,
packCcParticipateReq,
packCcRelReq,
packCcSetupReq,
packKeepaliveReq,
packMcChangeRsp,
packMcCheckRpt,
packMcDataReq,
packMcDataRsp,
packMcDataSessionPayload,
packMcJoinRsp,
packNativeGroupParticipateOffer,
packNativeSetupOffer,
packPlanetCcMsg,
Expand Down Expand Up @@ -308,9 +312,13 @@ const CASSINI_MSG_ID_CC_BASE = 0x2140;
const CASSINI_MSG_ID_SETUP_REQ = 0x2141;
const CASSINI_MSG_ID_REL_REQ = 0x2145;
const CASSINI_MSG_ID_GROUP_PARTICIPATE_REQ = 0x214e;
const CASSINI_MSG_ID_MC_JOIN_RSP = 0x3285;
const CASSINI_MSG_ID_MC_CHANGE_RSP = 0x3286;
const CASSINI_MSG_ID_MC_CHECK_RPT = 0x3287;
const CASSINI_MSG_ID_MC_DATA_REQ = 0x3189;
const CASSINI_MSG_ID_MC_DATA_RSP = 0x3289;
const CASSINI_MSG_ID_KEEPALIVE_REQ = 0x1101;
const CASSINI_MSG_ID_BEPI_OPEN = 0x1102;
const REGULAR_TAIL_CONTROL_BASE = 0x18;
const REGULAR_TAIL_RAW_BASE = 0x48;
const PINHOLE_PROBE_COUNT = 16;
Expand Down Expand Up @@ -623,9 +631,9 @@ function defaultAndroidUserAgent(deviceInfo?: string): PlanetUserAgent {
};
}

function defaultOneToOneDataSessionPayload(): Uint8Array {
function defaultOneToOneStrmSpec(): Uint8Array {
const state = { paused: false, code: 0 };
const strmSpec = packStrmSpec({
return packStrmSpec({
strms: [
{
ssrc: 102,
Expand Down Expand Up @@ -672,7 +680,10 @@ function defaultOneToOneDataSessionPayload(): Uint8Array {
probeBrMaxKbps: 200,
},
});
return packMcDataSessionPayload(strmSpec);
}

function defaultOneToOneDataSessionPayload(): Uint8Array {
return packMcDataSessionPayload(defaultOneToOneStrmSpec());
}

function defaultSetupFeatures(): Uint8Array[] {
Expand Down Expand Up @@ -942,6 +953,7 @@ export class PlanetTransport implements CallTransport {
#srtpSend?: SrtpCryptoContext;
#srtpRecv?: SrtpCryptoContext;
#groupDataSrtpSend?: SrtpCryptoContext;
#dataSrtpRecv?: SrtpCryptoContext;
#mediaKeyMode?: PlanetMediaKeyMode;
#mediaKeyCandidates: MediaKeyCandidate[] = [];
#rtp?: {
Expand Down Expand Up @@ -1039,7 +1051,7 @@ export class PlanetTransport implements CallTransport {
this.#localMediaChanId = BigInt(randomNativeGroupMediaChanId());
} else {
this.#srcChanId = BigInt(randomNativeLargeId());
this.#localMediaChanId = BigInt(randomNativeLargeId());
this.#localMediaChanId = this.#srcChanId;
}
this.#nextSeq = randomInitialFrameSeq();
this.#setupSent = false;
Expand All @@ -1061,6 +1073,7 @@ export class PlanetTransport implements CallTransport {
this.#srtpSend = undefined;
this.#srtpRecv = undefined;
this.#groupDataSrtpSend = undefined;
this.#dataSrtpRecv = undefined;
this.#mediaKeyMode = undefined;
this.#mediaKeyCandidates = [];
this.#rtp = undefined;
Expand Down Expand Up @@ -1228,6 +1241,20 @@ export class PlanetTransport implements CallTransport {
},
).catch(() => {});
}
if (msg.mc.bodyTag === MC_MSG.JOIN_REQ) {
void this.#sendMcJoinRsp(
incoming as PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
},
).catch(() => {});
}
if (msg.mc.bodyTag === MC_MSG.CHANGE_REQ) {
void this.#sendMcChangeRsp(
incoming as PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
},
).catch(() => {});
}
}
this.#debug({
type: "planet_msg",
Expand Down Expand Up @@ -2067,11 +2094,21 @@ export class PlanetTransport implements CallTransport {
this.#srtpRecv = initial.recvContext;
this.#groupDataSrtpSend = undefined;
this.#groupDataRtp = undefined;
this.#dataSrtpRecv = undefined;
if (this.#groupJoined && local.material.mediaSecret.length === 30) {
this.#groupDataSrtpSend = await deriveSrtpContext(
derivePlanetMediaStreamKeying(local.material.mediaSecret, "DATA"),
);
}
if (
!this.#groupJoined &&
local.material.mediaSecret.length === 30 &&
peerOffer.mediaSecret?.length === 30
) {
this.#dataSrtpRecv = await deriveSrtpContext(
derivePlanetMediaStreamKeying(peerOffer.mediaSecret, "DATA"),
);
}
this.#mediaKeyMode = requestedMode;
const fallbackHost = route.mediaHost ??
(this.#opts.preferIpv6 && route.cscfHost6
Expand Down Expand Up @@ -2245,6 +2282,98 @@ export class PlanetTransport implements CallTransport {
);
}

async #sendMcJoinRsp(
request: PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
},
): Promise<void> {
const rsp = packMcJoinRsp({
result: 0,
data: defaultOneToOneStrmSpec(),
});
const mcBody = wrapMcMsg(MC_MSG.JOIN_RSP, rsp);
const mcMsg = packPlanetMcMsg(
{
cid: request.message.mc?.hdr?.cid ?? this.#callUuid ?? "mc-join-rsp",
srcChanId: this.#localMediaChanId,
dstChanId: request.message.mc?.hdr?.srcChanId ??
this.#remoteMediaChanId,
},
mcBody,
);
this.#debug({ type: "mc_join_rsp_sent" });
await this.#sendEnvelope(
{ kind: "mc", data: mcMsg },
{ msgId: CASSINI_MSG_ID_MC_JOIN_RSP },
);
void this.#sendBepiChannelOpen().catch(() => {});
void this.#sendMcCheckRpt(request).catch(() => {});
}

async #sendMcChangeRsp(
request: PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
},
): Promise<void> {
const rsp = packMcChangeRsp({
result: 0,
data: defaultOneToOneStrmSpec(),
});
const mcBody = wrapMcMsg(MC_MSG.CHANGE_RSP, rsp);
const mcMsg = packPlanetMcMsg(
{
cid: request.message.mc?.hdr?.cid ?? this.#callUuid ?? "mc-change-rsp",
srcChanId: this.#localMediaChanId,
dstChanId: request.message.mc?.hdr?.srcChanId ??
this.#remoteMediaChanId,
},
mcBody,
);
this.#debug({ type: "mc_change_rsp_sent" });
await this.#sendEnvelope(
{ kind: "mc", data: mcMsg },
{ msgId: CASSINI_MSG_ID_MC_CHANGE_RSP },
);
}

async #sendMcCheckRpt(
request: PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
},
): Promise<void> {
const rpt = packMcCheckRpt(defaultOneToOneStrmSpec());
const mcBody = wrapMcMsg(MC_MSG.CHECK_RPT, rpt);
const mcMsg = packPlanetMcMsg(
{
cid: request.message.mc?.hdr?.cid ?? this.#callUuid ?? "mc-check-rpt",
srcChanId: this.#localMediaChanId,
dstChanId: request.message.mc?.hdr?.srcChanId ??
this.#remoteMediaChanId,
},
mcBody,
);
this.#debug({ type: "mc_check_rpt_sent" });
await this.#sendEnvelope(
{ kind: "mc", data: mcMsg },
{ msgId: CASSINI_MSG_ID_MC_CHECK_RPT },
);
}

async #sendBepiChannelOpen(): Promise<void> {
const token = BigInt(
"0x" +
Array.from(crypto.getRandomValues(new Uint8Array(8)))
.map((b) => b.toString(16).padStart(2, "0"))
.join(""),
);
const data = packBepiChannelOpen(token);
this.#debug({ type: "bepi_channel_open_sent" });
await this.#sendEnvelope(
{ kind: "sc", data },
{ msgId: CASSINI_MSG_ID_BEPI_OPEN },
);
}

async #sendDuplicateConnRsp(
request: PlanetIncomingMessage & {
message: ReturnType<typeof decodePlanetMsg>;
Expand Down
Loading