Skip to content

Commit c408c0d

Browse files
hughnsAndrewFerr
andauthored
Retry event decryption failures on first failure (#4346)
* Retry event decryption failures on first failure * Suggestion from code review Co-authored-by: Andrew Ferrazzutti <[email protected]> --------- Co-authored-by: Andrew Ferrazzutti <[email protected]>
1 parent d608039 commit c408c0d

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

spec/unit/matrixrtc/MatrixRTCSessionManager.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe("MatrixRTCSessionManager", () => {
102102
getContent: jest.fn().mockReturnValue({}),
103103
getSender: jest.fn().mockReturnValue("@mock:user.example"),
104104
getRoomId: jest.fn().mockReturnValue("!room:id"),
105+
isDecryptionFailure: jest.fn().mockReturnValue(false),
105106
sender: {
106107
userId: "@mock:user.example",
107108
},
@@ -110,4 +111,93 @@ describe("MatrixRTCSessionManager", () => {
110111
await new Promise(process.nextTick);
111112
expect(onCallEncryptionMock).toHaveBeenCalled();
112113
});
114+
115+
describe("event decryption", () => {
116+
it("Retries decryption and processes success", async () => {
117+
try {
118+
jest.useFakeTimers();
119+
const room1 = makeMockRoom([membershipTemplate]);
120+
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
121+
jest.spyOn(client, "getRoom").mockReturnValue(room1);
122+
123+
client.emit(ClientEvent.Room, room1);
124+
const onCallEncryptionMock = jest.fn();
125+
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
126+
let isDecryptionFailure = true;
127+
client.decryptEventIfNeeded = jest
128+
.fn()
129+
.mockReturnValueOnce(Promise.resolve())
130+
.mockImplementation(() => {
131+
isDecryptionFailure = false;
132+
return Promise.resolve();
133+
});
134+
const timelineEvent = {
135+
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
136+
getContent: jest.fn().mockReturnValue({}),
137+
getSender: jest.fn().mockReturnValue("@mock:user.example"),
138+
getRoomId: jest.fn().mockReturnValue("!room:id"),
139+
isDecryptionFailure: jest.fn().mockImplementation(() => isDecryptionFailure),
140+
getId: jest.fn().mockReturnValue("event_id"),
141+
sender: {
142+
userId: "@mock:user.example",
143+
},
144+
} as unknown as MatrixEvent;
145+
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
146+
147+
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
148+
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
149+
150+
// should retry after one second:
151+
await jest.advanceTimersByTimeAsync(1500);
152+
153+
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
154+
expect(onCallEncryptionMock).toHaveBeenCalledTimes(1);
155+
} finally {
156+
jest.useRealTimers();
157+
}
158+
});
159+
160+
it("Retries decryption and processes failure", async () => {
161+
try {
162+
jest.useFakeTimers();
163+
const room1 = makeMockRoom([membershipTemplate]);
164+
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
165+
jest.spyOn(client, "getRoom").mockReturnValue(room1);
166+
167+
client.emit(ClientEvent.Room, room1);
168+
const onCallEncryptionMock = jest.fn();
169+
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
170+
client.decryptEventIfNeeded = jest.fn().mockReturnValue(Promise.resolve());
171+
const timelineEvent = {
172+
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
173+
getContent: jest.fn().mockReturnValue({}),
174+
getSender: jest.fn().mockReturnValue("@mock:user.example"),
175+
getRoomId: jest.fn().mockReturnValue("!room:id"),
176+
isDecryptionFailure: jest.fn().mockReturnValue(true), // always fail
177+
getId: jest.fn().mockReturnValue("event_id"),
178+
sender: {
179+
userId: "@mock:user.example",
180+
},
181+
} as unknown as MatrixEvent;
182+
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
183+
184+
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
185+
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
186+
187+
// should retry after one second:
188+
await jest.advanceTimersByTimeAsync(1500);
189+
190+
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
191+
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
192+
193+
// doesn't retry again:
194+
await jest.advanceTimersByTimeAsync(1500);
195+
196+
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
197+
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
198+
} finally {
199+
jest.useRealTimers();
200+
}
201+
});
202+
});
113203
});

spec/unit/matrixrtc/mocks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,6 @@ export function mockRTCEvent(membershipData: MembershipData, roomId: string): Ma
7373
sender: {
7474
userId: "@mock:user.example",
7575
},
76+
isDecryptionFailure: jest.fn().mockReturnValue(false),
7677
} as unknown as MatrixEvent;
7778
}

src/matrixrtc/MatrixRTCSessionManager.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,23 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
9898
return this.roomSessions.get(room.roomId)!;
9999
}
100100

101-
private async consumeCallEncryptionEvent(event: MatrixEvent): Promise<void> {
101+
private async consumeCallEncryptionEvent(event: MatrixEvent, isRetry = false): Promise<void> {
102102
await this.client.decryptEventIfNeeded(event);
103+
if (event.isDecryptionFailure()) {
104+
if (!isRetry) {
105+
logger.warn(
106+
`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason} will retry once only`,
107+
);
108+
// retry after 1 second. After this we give up.
109+
setTimeout(() => this.consumeCallEncryptionEvent(event, true), 1000);
110+
} else {
111+
logger.warn(`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason}`);
112+
}
113+
return;
114+
} else if (isRetry) {
115+
logger.info(`Decryption succeeded for event ${event.getId()} after retry`);
116+
}
117+
103118
if (event.getType() !== EventType.CallEncryptionKeysPrefix) return Promise.resolve();
104119

105120
const room = this.client.getRoom(event.getRoomId());

0 commit comments

Comments
 (0)