From ff5fab7722e54be2d69a380cd6182ecb262d7859 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 8 Jan 2026 14:24:32 +0100 Subject: [PATCH 1/2] Use normal base64 encoding for RTC backend identities MSC4195 has been updated to specify that normal (non-URL-safe) base64 is the correct encoding for LiveKit participant identities. --- src/matrixrtc/CallMembership.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrixrtc/CallMembership.ts b/src/matrixrtc/CallMembership.ts index abecb70bb4..47912df315 100644 --- a/src/matrixrtc/CallMembership.ts +++ b/src/matrixrtc/CallMembership.ts @@ -22,7 +22,7 @@ import type { RTCCallIntent, Transport } from "./types.ts"; import { type MatrixEvent, type IContent } from "../models/event.ts"; import { type RelationType } from "../@types/event.ts"; import { sha256 } from "../digest.ts"; -import { encodeUnpaddedBase64Url } from "../base64.ts"; +import { encodeUnpaddedBase64 } from "../base64.ts"; import { type Logger } from "../logger.ts"; /** @@ -324,7 +324,7 @@ export class CallMembership { public static async computeRtcIdentityRaw(userId: string, deviceId: string, memberId: string): Promise { const hashInput = `${userId}|${deviceId}|${memberId}`; const hashBuffer = await sha256(hashInput); - const hashedString = encodeUnpaddedBase64Url(hashBuffer); + const hashedString = encodeUnpaddedBase64(hashBuffer); return hashedString; } From 2fb5ec92bb0f2140db0ffda245d0c793cf91960a Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 9 Jan 2026 18:20:59 +0100 Subject: [PATCH 2/2] Test RTC backend identity computation --- spec/unit/matrixrtc/CallMembership.spec.ts | 16 ++++++++++++++++ spec/unit/matrixrtc/MatrixRTCSession.spec.ts | 14 ++++++++++++++ src/matrixrtc/CallMembership.ts | 5 +---- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/spec/unit/matrixrtc/CallMembership.spec.ts b/spec/unit/matrixrtc/CallMembership.spec.ts index 0d18dbd234..453b1851c4 100644 --- a/spec/unit/matrixrtc/CallMembership.spec.ts +++ b/spec/unit/matrixrtc/CallMembership.spec.ts @@ -394,4 +394,20 @@ describe("CallMembership", () => { expect(membership.getMsUntilExpiry()).toEqual(DEFAULT_EXPIRE_DURATION - 1000); }); }); + + it("uses unpadded base64 for RTC backend identities", async () => { + expect( + await CallMembership.computeRtcBackendIdentity(makeMockEvent(), { + kind: "rtc", + data: { + slot_id: "m.call#", + application: { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" }, + member: { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzRANDOMxyz" }, + rtc_transports: [{ type: "livekit" }], + versions: [], + msc4354_sticky_key: "abc123", + }, + }), + ).toBe("2+h2ELE1XY/NsuveToZOekORCoyQMO6V0W7XZUWk5Q4"); + }); }); diff --git a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts index a02d20a163..de32f6ddd2 100644 --- a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts +++ b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts @@ -339,6 +339,20 @@ describe("MatrixRTCSession", () => { ); expect(sess.memberships).toHaveLength(0); }); + + it("assigns RTC backend identities to memberships", async () => { + const mockRoom = makeMockRoom([membershipTemplate], testConfig.testCreateSticky); + sess = MatrixRTCSession.sessionForSlot( + client, + mockRoom, + callSession, + testConfig.createWithDefaults ? undefined : testConfig, + ); + await flushPromises(); + expect(sess?.memberships.length).toEqual(1); + // Backend identity is expected to not be hashed with a legacy (session) membership + expect(sess?.memberships[0].rtcBackendIdentity).toEqual("@mock:user.example:AAAAAAA"); + }); }, ); diff --git a/src/matrixrtc/CallMembership.ts b/src/matrixrtc/CallMembership.ts index 47912df315..82bddca314 100644 --- a/src/matrixrtc/CallMembership.ts +++ b/src/matrixrtc/CallMembership.ts @@ -322,10 +322,7 @@ export class CallMembership { } public static async computeRtcIdentityRaw(userId: string, deviceId: string, memberId: string): Promise { - const hashInput = `${userId}|${deviceId}|${memberId}`; - const hashBuffer = await sha256(hashInput); - const hashedString = encodeUnpaddedBase64(hashBuffer); - return hashedString; + return encodeUnpaddedBase64(await sha256(`${userId}|${deviceId}|${memberId}`)); } public static membershipDataFromMatrixEvent(matrixEvent: MatrixEvent): MembershipData {