Skip to content

Commit 233fd85

Browse files
committed
test: Add RTC to device transport test
1 parent 0a587e9 commit 233fd85

File tree

6 files changed

+271
-10
lines changed

6 files changed

+271
-10
lines changed

spec/unit/matrixrtc/RoomKeyTransport.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport";
2020
import { EventType, MatrixClient, RoomEvent } from "../../../src";
2121
import type { IRoomTimelineData, MatrixEvent, Room } from "../../../src";
2222

23-
describe("RoomKyTransport", () => {
23+
describe("RoomKeyTransport", () => {
2424
let client: MatrixClient;
2525
let room: Room & {
2626
emitTimelineEvent: (event: MatrixEvent) => void;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*
2+
Copyright 2025 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { makeMockEvent, membershipTemplate, mockCallMembership } from "./mocks";
18+
import { ClientEvent, EventType, MatrixClient } from "../../../src";
19+
import { ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts";
20+
import { Mocked } from "jest-mock";
21+
import { getMockClientWithEventEmitter } from "../../test-utils/client.ts";
22+
import { Statistics } from "../../../src/matrixrtc";
23+
import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport.ts";
24+
import { defer } from "../../../src/utils.ts";
25+
import { Logger } from "../../../src/logger.ts";
26+
27+
describe("ToDeviceKeyTransport", () => {
28+
const roomId = "!room:id";
29+
30+
let mockClient: Mocked<MatrixClient>;
31+
let statistics: Statistics;
32+
let mockLogger: Mocked<Logger>;
33+
let transport: ToDeviceKeyTransport;
34+
35+
function setup() {
36+
mockClient = getMockClientWithEventEmitter({
37+
encryptAndSendToDevice: jest.fn(),
38+
});
39+
mockLogger = {
40+
debug: jest.fn(),
41+
warn: jest.fn(),
42+
} as unknown as Mocked<Logger>;
43+
statistics = {
44+
counters: {
45+
roomEventEncryptionKeysSent: 0,
46+
roomEventEncryptionKeysReceived: 0,
47+
},
48+
totals: {
49+
roomEventEncryptionKeysReceivedTotalAge: 0,
50+
},
51+
};
52+
53+
transport = new ToDeviceKeyTransport("@alice:example.org", "MYDEVICE", roomId, mockClient, statistics, {
54+
getChild: jest.fn().mockReturnValue(mockLogger),
55+
} as unknown as Mocked<Logger>);
56+
}
57+
58+
it("should send my keys on via to device", async () => {
59+
setup();
60+
61+
transport.start();
62+
63+
const keyBase64Encoded = "ABCDEDF";
64+
const keyIndex = 2;
65+
await transport.sendKey(keyBase64Encoded, keyIndex, [
66+
mockCallMembership(
67+
Object.assign({}, membershipTemplate, { device_id: "BOBDEVICE" }),
68+
roomId,
69+
"@bob:example.org",
70+
),
71+
mockCallMembership(
72+
Object.assign({}, membershipTemplate, { device_id: "CARLDEVICE" }),
73+
roomId,
74+
"@carl:example.org",
75+
),
76+
mockCallMembership(
77+
Object.assign({}, membershipTemplate, { device_id: "MATDEVICE" }),
78+
roomId,
79+
"@mat:example.org",
80+
),
81+
]);
82+
83+
expect(mockClient.encryptAndSendToDevice).toHaveBeenCalledTimes(1);
84+
expect(mockClient.encryptAndSendToDevice).toHaveBeenCalledWith(
85+
"io.element.call.encryption_keys",
86+
[
87+
{ userId: "@bob:example.org", deviceId: "BOBDEVICE" },
88+
{ userId: "@carl:example.org", deviceId: "CARLDEVICE" },
89+
{ userId: "@mat:example.org", deviceId: "MATDEVICE" },
90+
],
91+
{
92+
keys: {
93+
index: keyIndex,
94+
key: keyBase64Encoded,
95+
},
96+
member: {
97+
claimed_device_id: "MYDEVICE",
98+
},
99+
room_id: roomId,
100+
session: {
101+
application: "m.call",
102+
call_id: "",
103+
scope: "m.room",
104+
},
105+
},
106+
);
107+
108+
expect(statistics.counters.roomEventEncryptionKeysSent).toBe(1);
109+
});
110+
111+
it("should emit when a key is received", async () => {
112+
setup();
113+
114+
const deferred = defer<{ userId: string; deviceId: string; keyBase64Encoded: string; index: number }>();
115+
transport.on(KeyTransportEvents.ReceivedKeys, (userId, deviceId, keyBase64Encoded, index, timestamp) => {
116+
deferred.resolve({ userId, deviceId, keyBase64Encoded, index });
117+
});
118+
transport.start();
119+
120+
const testEncoded = "ABCDEDF";
121+
const testKeyIndex = 2;
122+
123+
mockClient.emit(
124+
ClientEvent.ToDeviceEvent,
125+
makeMockEvent(EventType.CallEncryptionKeysPrefix, "@bob:example.org", undefined, {
126+
keys: {
127+
index: testKeyIndex,
128+
key: testEncoded,
129+
},
130+
member: {
131+
claimed_device_id: "BOBDEVICE",
132+
},
133+
room_id: roomId,
134+
session: {
135+
application: "m.call",
136+
call_id: "",
137+
scope: "m.room",
138+
},
139+
}),
140+
);
141+
142+
const { userId, deviceId, keyBase64Encoded, index } = await deferred.promise;
143+
expect(userId).toBe("@bob:example.org");
144+
expect(deviceId).toBe("BOBDEVICE");
145+
expect(keyBase64Encoded).toBe(testEncoded);
146+
expect(index).toBe(testKeyIndex);
147+
148+
expect(statistics.counters.roomEventEncryptionKeysReceived).toBe(1);
149+
});
150+
151+
it("should not sent to ourself", async () => {
152+
setup();
153+
154+
const keyBase64Encoded = "ABCDEDF";
155+
const keyIndex = 2;
156+
await transport.sendKey(keyBase64Encoded, keyIndex, [
157+
mockCallMembership(
158+
Object.assign({}, membershipTemplate, { device_id: "MYDEVICE" }),
159+
roomId,
160+
"@alice:example.org",
161+
),
162+
]);
163+
164+
transport.start();
165+
166+
expect(mockClient.encryptAndSendToDevice).toHaveBeenCalledTimes(0);
167+
});
168+
169+
it("should warn when there is a room mismatch", async () => {
170+
setup();
171+
172+
transport.start();
173+
174+
const testEncoded = "ABCDEDF";
175+
const testKeyIndex = 2;
176+
177+
mockClient.emit(
178+
ClientEvent.ToDeviceEvent,
179+
makeMockEvent(EventType.CallEncryptionKeysPrefix, "@bob:example.org", undefined, {
180+
keys: {
181+
index: testKeyIndex,
182+
key: testEncoded,
183+
},
184+
member: {
185+
claimed_device_id: "BOBDEVICE",
186+
},
187+
room_id: "!anotherroom:id",
188+
session: {
189+
application: "m.call",
190+
call_id: "",
191+
scope: "m.room",
192+
},
193+
}),
194+
);
195+
196+
expect(mockLogger.warn).toHaveBeenCalledWith("Malformed Event: Mismatch roomId");
197+
expect(statistics.counters.roomEventEncryptionKeysReceived).toBe(0);
198+
});
199+
200+
describe("malformed events", () => {
201+
const MALFORMED_EVENT = [
202+
{
203+
keys: {},
204+
member: { claimed_device_id: "MYDEVICE" },
205+
room_id: "!room:id",
206+
session: { application: "m.call", call_id: "", scope: "m.room" },
207+
},
208+
{
209+
keys: { index: 0 },
210+
member: { claimed_device_id: "MYDEVICE" },
211+
room_id: "!room:id",
212+
session: { application: "m.call", call_id: "", scope: "m.room" },
213+
},
214+
{
215+
keys: { keys: "ABCDEF" },
216+
member: { claimed_device_id: "MYDEVICE" },
217+
room_id: "!room:id",
218+
session: { application: "m.call", call_id: "", scope: "m.room" },
219+
},
220+
{
221+
keys: { keys: "ABCDEF", index: 2 },
222+
room_id: "!room:id",
223+
session: { application: "m.call", call_id: "", scope: "m.room" },
224+
},
225+
{
226+
keys: { keys: "ABCDEF", index: 2 },
227+
member: {},
228+
room_id: "!room:id",
229+
session: { application: "m.call", call_id: "", scope: "m.room" },
230+
},
231+
{
232+
keys: { keys: "ABCDEF", index: 2 },
233+
member: { claimed_device_id: "MYDEVICE" },
234+
session: { application: "m.call", call_id: "", scope: "m.room" },
235+
},
236+
{
237+
keys: { keys: "ABCDEF", index: 2 },
238+
member: { claimed_device_id: "MYDEVICE" },
239+
room_id: "!room:id",
240+
session: { application: "m.call", call_id: "", scope: "m.room" },
241+
},
242+
];
243+
244+
test.each(MALFORMED_EVENT)("should warn on malformed event %j", async (event) => {
245+
setup();
246+
247+
transport.start();
248+
249+
mockClient.emit(
250+
ClientEvent.ToDeviceEvent,
251+
makeMockEvent(EventType.CallEncryptionKeysPrefix, "@bob:example.org", undefined, event),
252+
);
253+
254+
expect(mockLogger.warn).toHaveBeenCalled();
255+
expect(statistics.counters.roomEventEncryptionKeysReceived).toBe(0);
256+
});
257+
});
258+
});

spec/unit/matrixrtc/mocks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export function makeMockRoomState(membershipData: MembershipData, roomId: string
123123
export function makeMockEvent(
124124
type: string,
125125
sender: string,
126-
roomId: string,
126+
roomId: string | undefined,
127127
content: any,
128128
timestamp?: number,
129129
): MatrixEvent {

src/matrixrtc/MatrixRTCSession.ts

+1
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
388388
this.roomSubset.roomId,
389389
this.client,
390390
this.statistics,
391+
logger,
391392
);
392393
} else {
393394
transport = new RoomKeyTransport(this.roomSubset, this.client, this.statistics);

src/matrixrtc/ToDeviceKeyTransport.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ limitations under the License.
1616

1717
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
1818
import { type IKeyTransport, KeyTransportEvents, type KeyTransportEventsHandlerMap } from "./IKeyTransport.ts";
19-
import { type Logger, logger } from "../logger.ts";
19+
import { type Logger } from "../logger.ts";
2020
import type { CallMembership } from "./CallMembership.ts";
2121
import type { EncryptionKeysToDeviceEventContent, Statistics } from "./types.ts";
2222
import { ClientEvent, type MatrixClient } from "../client.ts";
@@ -35,9 +35,10 @@ export class ToDeviceKeyTransport
3535
private roomId: string,
3636
private client: Pick<MatrixClient, "encryptAndSendToDevice" | "on" | "off">,
3737
private statistics: Statistics,
38+
logger: Logger,
3839
) {
3940
super();
40-
this.prefixedLogger = logger.getChild(`[RTC: ${roomId} ToDeviceKeyTransport]`);
41+
this.prefixedLogger = logger.getChild(`[${roomId} ToDeviceKeyTransport]`);
4142
}
4243

4344
public start(): void {
@@ -54,7 +55,7 @@ export class ToDeviceKeyTransport
5455
index: index,
5556
key: keyBase64Encoded,
5657
},
57-
roomId: this.roomId,
58+
room_id: this.roomId,
5859
member: {
5960
claimed_device_id: this.deviceId,
6061
},
@@ -69,7 +70,7 @@ export class ToDeviceKeyTransport
6970
.filter((member) => {
7071
// filter malformed call members
7172
if (member.sender == undefined || member.deviceId == undefined) {
72-
logger.warn(`Malformed call member: ${member.sender}|${member.deviceId}`);
73+
this.prefixedLogger.warn(`Malformed call member: ${member.sender}|${member.deviceId}`);
7374
return false;
7475
}
7576
// Filter out me
@@ -84,6 +85,7 @@ export class ToDeviceKeyTransport
8485

8586
if (targets.length > 0) {
8687
await this.client.encryptAndSendToDevice(EventType.CallEncryptionKeysPrefix, targets, content);
88+
this.statistics.counters.roomEventEncryptionKeysSent += 1;
8789
} else {
8890
this.prefixedLogger.warn("No targets found for sending key");
8991
}
@@ -135,8 +137,8 @@ export class ToDeviceKeyTransport
135137
};
136138

137139
private getValidEventContent(event: MatrixEvent): EncryptionKeysToDeviceEventContent | undefined {
138-
const content = event.getContent<EncryptionKeysToDeviceEventContent>();
139-
const roomId = content.roomId;
140+
const content = event.getContent();
141+
const roomId = content.room_id;
140142
if (!roomId) {
141143
// Invalid event
142144
this.prefixedLogger.warn("Malformed Event: invalid call encryption keys event, no roomId");
@@ -162,6 +164,6 @@ export class ToDeviceKeyTransport
162164
// this.prefixedLogger.warn("Malformed Event: Missing/Malformed content.session", content.session);
163165
// return;
164166
// }
165-
return content;
167+
return content as EncryptionKeysToDeviceEventContent;
166168
}
167169
}

src/matrixrtc/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface EncryptionKeysToDeviceEventContent {
3636
claimed_device_id: string;
3737
// user_id: string
3838
};
39-
roomId: string;
39+
room_id: string;
4040
session: {
4141
application: string;
4242
call_id: string;

0 commit comments

Comments
 (0)