mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-07 21:45:18 +00:00
Log track encryption flag and cryptor events for remote tracks
Extends the [RemoteTracks] logging from #4235 with the per-publication encryption flag and the ParticipantEncryptionStatusChanged / EncryptionError room events. If a publisher encrypts frames while the subscriber believes the publication is unencrypted, livekit-client bypasses the cryptor and hands raw ciphertext to the decoder, which is audible as loud noise bursts. The reverse mismatch (or a missing/invalid key) drops frames instead. Neither case is visible in a rageshake today.
This commit is contained in:
@@ -516,11 +516,12 @@ describe("remote track logging", () => {
|
|||||||
it("logs remote participant and track events on the connection logger", () => {
|
it("logs remote participant and track events on the connection logger", () => {
|
||||||
setupTest();
|
setupTest();
|
||||||
const info = vi.fn();
|
const info = vi.fn();
|
||||||
|
const warn = vi.fn();
|
||||||
const testLogger = {
|
const testLogger = {
|
||||||
getChild: (): unknown => testLogger,
|
getChild: (): unknown => testLogger,
|
||||||
info,
|
info,
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn,
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
} as unknown as Logger;
|
} as unknown as Logger;
|
||||||
new Connection(
|
new Connection(
|
||||||
@@ -545,6 +546,7 @@ describe("remote track logging", () => {
|
|||||||
source: "microphone",
|
source: "microphone",
|
||||||
trackSid: "TR_mic",
|
trackSid: "TR_mic",
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
|
isEncrypted: true,
|
||||||
} as unknown as RemoteTrackPublication;
|
} as unknown as RemoteTrackPublication;
|
||||||
const messages = (): string[] => info.mock.calls.map((c) => c[0] as string);
|
const messages = (): string[] => info.mock.calls.map((c) => c[0] as string);
|
||||||
|
|
||||||
@@ -564,11 +566,27 @@ describe("remote track logging", () => {
|
|||||||
);
|
);
|
||||||
expect(messages()).toEqual([
|
expect(messages()).toEqual([
|
||||||
"Participant connected: @bob:example.org:DEV111 (PA_bob)",
|
"Participant connected: @bob:example.org:DEV111 (PA_bob)",
|
||||||
"Subscribed: audio microphone TR_mic of @bob:example.org:DEV111 muted=false",
|
"Subscribed: audio microphone TR_mic of @bob:example.org:DEV111 encrypted=true muted=false",
|
||||||
"Muted: audio microphone TR_mic of @bob:example.org:DEV111",
|
"Muted: audio microphone TR_mic of @bob:example.org:DEV111 encrypted=true",
|
||||||
"Stream paused: audio microphone TR_mic of @bob:example.org:DEV111",
|
"Stream paused: audio microphone TR_mic of @bob:example.org:DEV111 encrypted=true",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Encryption status changes are logged; cryptor errors are warnings
|
||||||
|
fakeLivekitRoom.emit(
|
||||||
|
RoomEvent.ParticipantEncryptionStatusChanged,
|
||||||
|
false,
|
||||||
|
bob,
|
||||||
|
);
|
||||||
|
expect(messages().at(-1)).toBe(
|
||||||
|
"Encryption status of @bob:example.org:DEV111: encrypted=false",
|
||||||
|
);
|
||||||
|
const cryptorError = new Error("missing key at index 3");
|
||||||
|
fakeLivekitRoom.emit(RoomEvent.EncryptionError, cryptorError, bob);
|
||||||
|
expect(warn).toHaveBeenCalledWith(
|
||||||
|
"Encryption error for @bob:example.org:DEV111:",
|
||||||
|
cryptorError,
|
||||||
|
);
|
||||||
|
|
||||||
// Local mute events are already logged by the Publisher
|
// Local mute events are already logged by the Publisher
|
||||||
fakeLivekitRoom.emit(RoomEvent.TrackMuted, pub, {
|
fakeLivekitRoom.emit(RoomEvent.TrackMuted, pub, {
|
||||||
...fakeLocalParticipant,
|
...fakeLocalParticipant,
|
||||||
|
|||||||
@@ -186,8 +186,11 @@ export class Connection {
|
|||||||
private logRemoteTrackEvents(): void {
|
private logRemoteTrackEvents(): void {
|
||||||
const room = this.livekitRoom;
|
const room = this.livekitRoom;
|
||||||
const log = this.logger.getChild("[RemoteTracks]");
|
const log = this.logger.getChild("[RemoteTracks]");
|
||||||
|
// The encryption flag matters: if the publisher encrypts but this client
|
||||||
|
// believes the track is unencrypted, the cryptor is bypassed and raw
|
||||||
|
// ciphertext reaches the decoder (audible as loud noise bursts).
|
||||||
const track = (pub: TrackPublication, p: Participant): string =>
|
const track = (pub: TrackPublication, p: Participant): string =>
|
||||||
`${pub.kind} ${pub.source} ${pub.trackSid} of ${p.identity}`;
|
`${pub.kind} ${pub.source} ${pub.trackSid} of ${p.identity} encrypted=${pub.isEncrypted}`;
|
||||||
|
|
||||||
const onParticipantConnected = (p: RemoteParticipant): void =>
|
const onParticipantConnected = (p: RemoteParticipant): void =>
|
||||||
log.info(`Participant connected: ${p.identity} (${p.sid})`);
|
log.info(`Participant connected: ${p.identity} (${p.sid})`);
|
||||||
@@ -232,6 +235,20 @@ export class Connection {
|
|||||||
state: Track.StreamState,
|
state: Track.StreamState,
|
||||||
p: RemoteParticipant,
|
p: RemoteParticipant,
|
||||||
): void => log.info(`Stream ${state}: ${track(pub, p)}`);
|
): void => log.info(`Stream ${state}: ${track(pub, p)}`);
|
||||||
|
const onEncryptionStatusChanged = (
|
||||||
|
encrypted: boolean,
|
||||||
|
p?: Participant,
|
||||||
|
): void =>
|
||||||
|
log.info(
|
||||||
|
`Encryption status of ${p?.identity ?? "unknown participant"}: encrypted=${encrypted}`,
|
||||||
|
);
|
||||||
|
// livekit-client throttles these per cryptor; they indicate frames being
|
||||||
|
// dropped (missing/invalid key), which is the other half of the picture.
|
||||||
|
const onEncryptionError = (error: Error, p?: Participant): void =>
|
||||||
|
log.warn(
|
||||||
|
`Encryption error for ${p?.identity ?? "unknown participant"}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
room
|
room
|
||||||
.on(RoomEvent.ParticipantConnected, onParticipantConnected)
|
.on(RoomEvent.ParticipantConnected, onParticipantConnected)
|
||||||
@@ -243,7 +260,12 @@ export class Connection {
|
|||||||
.on(RoomEvent.TrackSubscriptionFailed, onTrackSubscriptionFailed)
|
.on(RoomEvent.TrackSubscriptionFailed, onTrackSubscriptionFailed)
|
||||||
.on(RoomEvent.TrackMuted, onTrackMuted)
|
.on(RoomEvent.TrackMuted, onTrackMuted)
|
||||||
.on(RoomEvent.TrackUnmuted, onTrackUnmuted)
|
.on(RoomEvent.TrackUnmuted, onTrackUnmuted)
|
||||||
.on(RoomEvent.TrackStreamStateChanged, onTrackStreamStateChanged);
|
.on(RoomEvent.TrackStreamStateChanged, onTrackStreamStateChanged)
|
||||||
|
.on(
|
||||||
|
RoomEvent.ParticipantEncryptionStatusChanged,
|
||||||
|
onEncryptionStatusChanged,
|
||||||
|
)
|
||||||
|
.on(RoomEvent.EncryptionError, onEncryptionError);
|
||||||
this.scope.onEnd(() => {
|
this.scope.onEnd(() => {
|
||||||
room
|
room
|
||||||
.off(RoomEvent.ParticipantConnected, onParticipantConnected)
|
.off(RoomEvent.ParticipantConnected, onParticipantConnected)
|
||||||
@@ -255,7 +277,12 @@ export class Connection {
|
|||||||
.off(RoomEvent.TrackSubscriptionFailed, onTrackSubscriptionFailed)
|
.off(RoomEvent.TrackSubscriptionFailed, onTrackSubscriptionFailed)
|
||||||
.off(RoomEvent.TrackMuted, onTrackMuted)
|
.off(RoomEvent.TrackMuted, onTrackMuted)
|
||||||
.off(RoomEvent.TrackUnmuted, onTrackUnmuted)
|
.off(RoomEvent.TrackUnmuted, onTrackUnmuted)
|
||||||
.off(RoomEvent.TrackStreamStateChanged, onTrackStreamStateChanged);
|
.off(RoomEvent.TrackStreamStateChanged, onTrackStreamStateChanged)
|
||||||
|
.off(
|
||||||
|
RoomEvent.ParticipantEncryptionStatusChanged,
|
||||||
|
onEncryptionStatusChanged,
|
||||||
|
)
|
||||||
|
.off(RoomEvent.EncryptionError, onEncryptionError);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user