You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Retry event decryption failures on first failure (#4346)
* Retry event decryption failures on first failure * Suggestion from code review Co-authored-by: Andrew Ferrazzutti <andrewf@element.io> --------- Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
This commit is contained in:
@@ -102,6 +102,7 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
getContent: jest.fn().mockReturnValue({}),
|
getContent: jest.fn().mockReturnValue({}),
|
||||||
getSender: jest.fn().mockReturnValue("@mock:user.example"),
|
getSender: jest.fn().mockReturnValue("@mock:user.example"),
|
||||||
getRoomId: jest.fn().mockReturnValue("!room:id"),
|
getRoomId: jest.fn().mockReturnValue("!room:id"),
|
||||||
|
isDecryptionFailure: jest.fn().mockReturnValue(false),
|
||||||
sender: {
|
sender: {
|
||||||
userId: "@mock:user.example",
|
userId: "@mock:user.example",
|
||||||
},
|
},
|
||||||
@@ -110,4 +111,93 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
await new Promise(process.nextTick);
|
await new Promise(process.nextTick);
|
||||||
expect(onCallEncryptionMock).toHaveBeenCalled();
|
expect(onCallEncryptionMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("event decryption", () => {
|
||||||
|
it("Retries decryption and processes success", async () => {
|
||||||
|
try {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const room1 = makeMockRoom([membershipTemplate]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
const onCallEncryptionMock = jest.fn();
|
||||||
|
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
|
||||||
|
let isDecryptionFailure = true;
|
||||||
|
client.decryptEventIfNeeded = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValueOnce(Promise.resolve())
|
||||||
|
.mockImplementation(() => {
|
||||||
|
isDecryptionFailure = false;
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
const timelineEvent = {
|
||||||
|
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
|
||||||
|
getContent: jest.fn().mockReturnValue({}),
|
||||||
|
getSender: jest.fn().mockReturnValue("@mock:user.example"),
|
||||||
|
getRoomId: jest.fn().mockReturnValue("!room:id"),
|
||||||
|
isDecryptionFailure: jest.fn().mockImplementation(() => isDecryptionFailure),
|
||||||
|
getId: jest.fn().mockReturnValue("event_id"),
|
||||||
|
sender: {
|
||||||
|
userId: "@mock:user.example",
|
||||||
|
},
|
||||||
|
} as unknown as MatrixEvent;
|
||||||
|
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
|
||||||
|
|
||||||
|
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
// should retry after one second:
|
||||||
|
await jest.advanceTimersByTimeAsync(1500);
|
||||||
|
|
||||||
|
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onCallEncryptionMock).toHaveBeenCalledTimes(1);
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Retries decryption and processes failure", async () => {
|
||||||
|
try {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const room1 = makeMockRoom([membershipTemplate]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
const onCallEncryptionMock = jest.fn();
|
||||||
|
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
|
||||||
|
client.decryptEventIfNeeded = jest.fn().mockReturnValue(Promise.resolve());
|
||||||
|
const timelineEvent = {
|
||||||
|
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
|
||||||
|
getContent: jest.fn().mockReturnValue({}),
|
||||||
|
getSender: jest.fn().mockReturnValue("@mock:user.example"),
|
||||||
|
getRoomId: jest.fn().mockReturnValue("!room:id"),
|
||||||
|
isDecryptionFailure: jest.fn().mockReturnValue(true), // always fail
|
||||||
|
getId: jest.fn().mockReturnValue("event_id"),
|
||||||
|
sender: {
|
||||||
|
userId: "@mock:user.example",
|
||||||
|
},
|
||||||
|
} as unknown as MatrixEvent;
|
||||||
|
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
|
||||||
|
|
||||||
|
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
// should retry after one second:
|
||||||
|
await jest.advanceTimersByTimeAsync(1500);
|
||||||
|
|
||||||
|
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
// doesn't retry again:
|
||||||
|
await jest.advanceTimersByTimeAsync(1500);
|
||||||
|
|
||||||
|
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -73,5 +73,6 @@ export function mockRTCEvent(membershipData: MembershipData, roomId: string): Ma
|
|||||||
sender: {
|
sender: {
|
||||||
userId: "@mock:user.example",
|
userId: "@mock:user.example",
|
||||||
},
|
},
|
||||||
|
isDecryptionFailure: jest.fn().mockReturnValue(false),
|
||||||
} as unknown as MatrixEvent;
|
} as unknown as MatrixEvent;
|
||||||
}
|
}
|
||||||
|
@@ -98,8 +98,23 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
|||||||
return this.roomSessions.get(room.roomId)!;
|
return this.roomSessions.get(room.roomId)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async consumeCallEncryptionEvent(event: MatrixEvent): Promise<void> {
|
private async consumeCallEncryptionEvent(event: MatrixEvent, isRetry = false): Promise<void> {
|
||||||
await this.client.decryptEventIfNeeded(event);
|
await this.client.decryptEventIfNeeded(event);
|
||||||
|
if (event.isDecryptionFailure()) {
|
||||||
|
if (!isRetry) {
|
||||||
|
logger.warn(
|
||||||
|
`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason} will retry once only`,
|
||||||
|
);
|
||||||
|
// retry after 1 second. After this we give up.
|
||||||
|
setTimeout(() => this.consumeCallEncryptionEvent(event, true), 1000);
|
||||||
|
} else {
|
||||||
|
logger.warn(`Decryption failed for event ${event.getId()}: ${event.decryptionFailureReason}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (isRetry) {
|
||||||
|
logger.info(`Decryption succeeded for event ${event.getId()} after retry`);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.getType() !== EventType.CallEncryptionKeysPrefix) return Promise.resolve();
|
if (event.getType() !== EventType.CallEncryptionKeysPrefix) return Promise.resolve();
|
||||||
|
|
||||||
const room = this.client.getRoom(event.getRoomId());
|
const room = this.client.getRoom(event.getRoomId());
|
||||||
|
Reference in New Issue
Block a user