diff --git a/app/providers/CallProvider/audioE2EE.ts b/app/providers/CallProvider/audioE2EE.ts index 97fbd6e..216d065 100644 --- a/app/providers/CallProvider/audioE2EE.ts +++ b/app/providers/CallProvider/audioE2EE.ts @@ -1,31 +1,30 @@ function toArrayBuffer(src: Buffer | Uint8Array): ArrayBuffer { - const u8 = src instanceof Uint8Array ? src : new Uint8Array(src); - return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer; + const u8 = src instanceof Uint8Array ? src : new Uint8Array(src); + return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer; } type KeyInput = Buffer | Uint8Array; async function importAesCtrKey(input: KeyInput): Promise { -console.info("Importing AES-CTR key for E2EE:", Buffer.from(input).toString('hex')); - const keyBytes = toArrayBuffer(input); - if (keyBytes.byteLength !== 32) { - throw new Error(`E2EE key must be 32 bytes, got ${keyBytes.byteLength}`); - } + const keyBytes = toArrayBuffer(input); + if (keyBytes.byteLength !== 32) { + throw new Error(`E2EE key must be 32 bytes, got ${keyBytes.byteLength}`); + } - return crypto.subtle.importKey( - "raw", - keyBytes, - { name: "AES-CTR" }, - false, - ["encrypt", "decrypt"] - ); + return crypto.subtle.importKey( + "raw", + keyBytes, + { name: "AES-CTR" }, + false, + ["encrypt", "decrypt"] + ); } function toBigIntTs(ts: unknown): bigint { - if (typeof ts === "bigint") return ts; - if (typeof ts === "number") return BigInt(ts); - return 0n; + if (typeof ts === "bigint") return ts; + if (typeof ts === "number") return BigInt(ts); + return 0n; } /** @@ -35,72 +34,72 @@ function toBigIntTs(ts: unknown): bigint { * [12..15] reserved */ function buildCounter(direction: number, timestamp: unknown): ArrayBuffer { - const iv = new Uint8Array(16); - const dv = new DataView(iv.buffer); - dv.setUint32(0, direction >>> 0, false); - dv.setBigUint64(4, toBigIntTs(timestamp), false); - dv.setUint32(12, 0, false); - return toArrayBuffer(iv); + const iv = new Uint8Array(16); + const dv = new DataView(iv.buffer); + dv.setUint32(0, direction >>> 0, false); + dv.setBigUint64(4, toBigIntTs(timestamp), false); + dv.setUint32(12, 0, false); + return toArrayBuffer(iv); } export async function attachSenderE2EE(sender: RTCRtpSender, keyInput: KeyInput): Promise { - const key = await importAesCtrKey(keyInput); + const key = await importAesCtrKey(keyInput); - const anySender = sender as unknown as { - createEncodedStreams?: () => { readable: ReadableStream; writable: WritableStream }; - }; + const anySender = sender as unknown as { + createEncodedStreams?: () => { readable: ReadableStream; writable: WritableStream }; + }; - if (!anySender.createEncodedStreams) { - throw new Error("createEncodedStreams is not available on RTCRtpSender"); - } - - const { readable, writable } = anySender.createEncodedStreams(); - - const enc = new TransformStream({ - async transform(frame, controller) { - const counter = buildCounter(1, frame.timestamp); - const encrypted = await crypto.subtle.encrypt( - { name: "AES-CTR", counter, length: 64 }, - key, - frame.data - ); - frame.data = encrypted; // same length - controller.enqueue(frame); + if (!anySender.createEncodedStreams) { + throw new Error("createEncodedStreams is not available on RTCRtpSender"); } - }); - readable.pipeThrough(enc).pipeTo(writable).catch((e) => { - console.error("Sender E2EE pipeline failed:", e); - }); + const { readable, writable } = anySender.createEncodedStreams(); + + const enc = new TransformStream({ + async transform(frame, controller) { + const counter = buildCounter(1, frame.timestamp); + const encrypted = await crypto.subtle.encrypt( + { name: "AES-CTR", counter, length: 64 }, + key, + frame.data + ); + frame.data = encrypted; // same length + controller.enqueue(frame); + } + }); + + readable.pipeThrough(enc).pipeTo(writable).catch((e) => { + console.error("Sender E2EE pipeline failed:", e); + }); } export async function attachReceiverE2EE(receiver: RTCRtpReceiver, keyInput: KeyInput): Promise { - const key = await importAesCtrKey(keyInput); + const key = await importAesCtrKey(keyInput); - const anyReceiver = receiver as unknown as { - createEncodedStreams?: () => { readable: ReadableStream; writable: WritableStream }; - }; + const anyReceiver = receiver as unknown as { + createEncodedStreams?: () => { readable: ReadableStream; writable: WritableStream }; + }; - if (!anyReceiver.createEncodedStreams) { - throw new Error("createEncodedStreams is not available on RTCRtpReceiver"); - } - - const { readable, writable } = anyReceiver.createEncodedStreams(); - - const dec = new TransformStream({ - async transform(frame, controller) { - const counter = buildCounter(1, frame.timestamp); - const decrypted = await crypto.subtle.decrypt( - { name: "AES-CTR", counter, length: 64 }, - key, - frame.data - ); - frame.data = decrypted; // same length - controller.enqueue(frame); + if (!anyReceiver.createEncodedStreams) { + throw new Error("createEncodedStreams is not available on RTCRtpReceiver"); } - }); - readable.pipeThrough(dec).pipeTo(writable).catch((e) => { - console.error("Receiver E2EE pipeline failed:", e); - }); + const { readable, writable } = anyReceiver.createEncodedStreams(); + + const dec = new TransformStream({ + async transform(frame, controller) { + const counter = buildCounter(1, frame.timestamp); + const decrypted = await crypto.subtle.decrypt( + { name: "AES-CTR", counter, length: 64 }, + key, + frame.data + ); + frame.data = decrypted; // same length + controller.enqueue(frame); + } + }); + + readable.pipeThrough(dec).pipeTo(writable).catch((e) => { + console.error("Receiver E2EE pipeline failed:", e); + }); } \ No newline at end of file