Skip to content

Commit

Permalink
Propagate reason for chat disconnect to listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
akonradi-signal authored Sep 5, 2024
1 parent 823cfcf commit 40aaecb
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private NativeTesting() {}
public static native Object TESTING_ChatServiceResponseAndDebugInfoConvert() throws Exception;
public static native Object TESTING_ChatServiceResponseConvert(boolean bodyPresent) throws Exception;
public static native void TESTING_ChatService_InjectConnectionInterrupted(long chat);
public static native void TESTING_ChatService_InjectIntentionalDisconnect(long chat);
public static native void TESTING_ChatService_InjectRawServerRequest(long chat, byte[] bytes);
public static native void TESTING_ErrorOnBorrowAsync(Object input);
public static native CompletableFuture TESTING_ErrorOnBorrowIo(long asyncRuntime, Object input);
Expand Down
5 changes: 4 additions & 1 deletion node/Native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
//

import { LibSignalError } from './ts/Errors';

// WARNING: this file was automatically generated

type Uuid = Buffer;
Expand Down Expand Up @@ -120,7 +122,7 @@ export abstract class ChatListener {
ack: ServerMessageAck
): void;
_queue_empty(): void;
_connection_interrupted(): void;
_connection_interrupted(reason: LibSignalError | null): void;
}

export abstract class MakeChatListener extends ChatListener {}
Expand Down Expand Up @@ -498,6 +500,7 @@ export function TESTING_ChatServiceErrorConvert(errorDescription: string): void;
export function TESTING_ChatServiceResponseAndDebugInfoConvert(): ResponseAndDebugInfo;
export function TESTING_ChatServiceResponseConvert(bodyPresent: boolean): ChatResponse;
export function TESTING_ChatService_InjectConnectionInterrupted(chat: Wrapper<Chat>): void;
export function TESTING_ChatService_InjectIntentionalDisconnect(chat: Wrapper<Chat>): void;
export function TESTING_ChatService_InjectRawServerRequest(chat: Wrapper<Chat>, bytes: Buffer): void;
export function TESTING_ErrorOnBorrowAsync(_input: null): Promise<void>;
export function TESTING_ErrorOnBorrowIo(asyncRuntime: Wrapper<NonSuspendingBackgroundThreadRuntime>, _input: null): Promise<void>;
Expand Down
15 changes: 8 additions & 7 deletions node/ts/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ export interface ConnectionEventsListener {
/**
* Called when the client gets disconnected from the server.
*
* This includes both deliberate disconnects as well as unexpected socket closures that will be
* automatically retried.
* This includes both deliberate disconnects as well as unexpected socket
* closures. If the closure was not due to a deliberate disconnect, the error
* will be provided.
*/
onConnectionInterrupted(): void;
onConnectionInterrupted(cause: LibSignalError | null): void;
}

export interface ChatServiceListener extends ConnectionEventsListener {
Expand Down Expand Up @@ -251,8 +252,8 @@ export class AuthenticatedChatService implements ChatService {
_queue_empty(): void {
listener.onQueueEmpty();
},
_connection_interrupted(): void {
listener.onConnectionInterrupted();
_connection_interrupted(cause: LibSignalError | null): void {
listener.onConnectionInterrupted(cause);
},
};
Native.ChatService_SetListenerAuth(
Expand Down Expand Up @@ -331,8 +332,8 @@ export class UnauthenticatedChatService implements ChatService {
_queue_empty(): void {
throw new Error('Event not supported on unauthenticated connection');
},
_connection_interrupted(): void {
listener.onConnectionInterrupted();
_connection_interrupted(cause: LibSignalError | null): void {
listener.onConnectionInterrupted(cause);
},
};
Native.ChatService_SetListenerUnauth(
Expand Down
67 changes: 56 additions & 11 deletions node/ts/test/NetTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { randomBytes } from 'crypto';
import { ChatResponse } from '../../Native';
import { CompletablePromise } from './util';
import { fail } from 'assert';

use(chaiAsPromised);
use(sinonChai);
Expand Down Expand Up @@ -290,15 +291,23 @@ describe('chat service api', () => {
INVALID_MESSAGE,
INCOMING_MESSAGE_2,
];
const callsReceived: string[] = [];
const callsExpected: string[] = [
'_incoming_message',
'_queue_empty',
'_incoming_message',
'_connection_interrupted',
const callsReceived: [string, (object | null)[]][] = [];
const callsExpected: [string, ((value: object | null) => void)[]][] = [
['_incoming_message', []],
['_queue_empty', []],
['_incoming_message', []],
[
'_connection_interrupted',
[
(error: object | null) =>
expect(error)
.instanceOf(LibSignalErrorBase)
.property('code', ErrorCode.IoError),
],
],
];
const recordCall = function (name: string) {
callsReceived.push(name);
const recordCall = function (name: string, ...args: (object | null)[]) {
callsReceived.push([name, args]);
if (callsReceived.length == callsExpected.length) {
completable.complete();
}
Expand All @@ -314,8 +323,8 @@ describe('chat service api', () => {
onQueueEmpty(): void {
recordCall('_queue_empty');
},
onConnectionInterrupted(): void {
recordCall('_connection_interrupted');
onConnectionInterrupted(cause: object | null): void {
recordCall('_connection_interrupted', cause);
},
};
const chat = net.newAuthenticatedChatService('', '', false, listener);
Expand All @@ -327,7 +336,43 @@ describe('chat service api', () => {
);
Native.TESTING_ChatService_InjectConnectionInterrupted(chat.chatService);
await completable.done();
expect(callsReceived).to.eql(callsExpected);

expect(callsReceived).to.have.lengthOf(callsExpected.length);
callsReceived.forEach((element, index) => {
const [call, args] = element;
const [expectedCall, expectedArgs] = callsExpected[index];
expect(call).to.eql(expectedCall);
expect(args.length).to.eql(expectedArgs.length);
args.map((arg, i) => {
expectedArgs[i](arg);
});
});
});

it('listener gets null cause for intentional disconnect', async () => {
const net = new Net(Environment.Staging, userAgent);
const completable = new CompletablePromise();
const connectionInterruptedReasons: (object | null)[] = [];
const listener: ChatServiceListener = {
onIncomingMessage(
_envelope: Buffer,
_timestamp: number,
_ack: ChatServerMessageAck
): void {
fail('unexpected call');
},
onQueueEmpty(): void {
fail('unexpected call');
},
onConnectionInterrupted(cause: object | null): void {
connectionInterruptedReasons.push(cause);
completable.complete();
},
};
const chat = net.newAuthenticatedChatService('', '', false, listener);
Native.TESTING_ChatService_InjectIntentionalDisconnect(chat.chatService);
await completable.done();
expect(connectionInterruptedReasons).to.eql([null]);
});

it('client can respond with http status code to a server message', () => {
Expand Down
4 changes: 3 additions & 1 deletion rust/bridge/node/bin/Native.d.ts.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
//

import { LibSignalError } from './ts/Errors';

// WARNING: this file was automatically generated

type Uuid = Buffer;
Expand Down Expand Up @@ -120,7 +122,7 @@ export abstract class ChatListener {
ack: ServerMessageAck
): void;
_queue_empty(): void;
_connection_interrupted(): void;
_connection_interrupted(reason: LibSignalError | null): void;
}

export abstract class MakeChatListener extends ChatListener {}
Expand Down
13 changes: 13 additions & 0 deletions rust/bridge/shared/testing/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ make_error_testing_enum! {
AllConnectionRoutesFailed => AllConnectionRoutesFailed,
ServiceInactive => ServiceInactive,
ServiceUnavailable => ServiceUnavailable,
ServiceIntentionallyDisconnected => ServiceIntentionallyDisconnected,
}
}

Expand Down Expand Up @@ -185,6 +186,9 @@ fn TESTING_ChatServiceErrorConvert(
}
TestingChatServiceError::ServiceInactive => ChatServiceError::ServiceInactive,
TestingChatServiceError::ServiceUnavailable => ChatServiceError::ServiceUnavailable,
TestingChatServiceError::ServiceIntentionallyDisconnected => {
ChatServiceError::ServiceIntentionallyDisconnected
}
})
}

Expand Down Expand Up @@ -278,6 +282,15 @@ fn TESTING_ChatService_InjectConnectionInterrupted(chat: &Chat) {
.expect("not closed");
}

#[bridge_fn]
fn TESTING_ChatService_InjectIntentionalDisconnect(chat: &Chat) {
chat.synthetic_request_tx
.blocking_send(chat::ws::ServerEvent::Stopped(
ChatServiceError::ServiceIntentionallyDisconnected,
))
.expect("not closed");
}

#[bridge_fn(jni = false, ffi = false)]
fn TESTING_ServerMessageAck_Create() -> ServerMessageAck {
ServerMessageAck::new(Box::new(|_| Box::pin(std::future::ready(Ok(())))))
Expand Down
49 changes: 28 additions & 21 deletions rust/bridge/shared/types/src/ffi/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,34 +75,35 @@ pub enum SignalErrorCode {
UsernameLinkInvalidEntropyDataLength = 127,
UsernameLinkInvalid = 128,

UsernameDiscriminatorCannotBeEmpty = 140,
UsernameDiscriminatorCannotBeZero = 141,
UsernameDiscriminatorCannotBeSingleDigit = 142,
UsernameDiscriminatorCannotHaveLeadingZeros = 143,
UsernameDiscriminatorTooLarge = 144,
UsernameDiscriminatorCannotBeEmpty = 130,
UsernameDiscriminatorCannotBeZero = 131,
UsernameDiscriminatorCannotBeSingleDigit = 132,
UsernameDiscriminatorCannotHaveLeadingZeros = 133,
UsernameDiscriminatorTooLarge = 134,

IoError = 130,
IoError = 140,
#[allow(dead_code)]
InvalidMediaInput = 131,
InvalidMediaInput = 141,
#[allow(dead_code)]
UnsupportedMediaInput = 132,
UnsupportedMediaInput = 142,

ConnectionTimedOut = 133,
NetworkProtocol = 134,
RateLimited = 135,
WebSocket = 136,
CdsiInvalidToken = 137,
ConnectionFailed = 138,
ChatServiceInactive = 139,
ConnectionTimedOut = 143,
NetworkProtocol = 144,
RateLimited = 145,
WebSocket = 146,
CdsiInvalidToken = 147,
ConnectionFailed = 148,
ChatServiceInactive = 149,
ChatServiceIntentionallyDisconnected = 150,

SvrDataMissing = 150,
SvrRestoreFailed = 151,
SvrRotationMachineTooManySteps = 152,
SvrDataMissing = 160,
SvrRestoreFailed = 161,
SvrRotationMachineTooManySteps = 162,

AppExpired = 160,
DeviceDeregistered = 161,
AppExpired = 170,
DeviceDeregistered = 171,

BackupValidation = 170,
BackupValidation = 180,
}

pub trait UpcastAsAny {
Expand Down Expand Up @@ -546,6 +547,9 @@ impl FfiError for ChatServiceError {
Self::ServiceInactive => "Chat service inactive".to_owned(),
Self::AppExpired => "App expired".to_owned(),
Self::DeviceDeregistered => "Device deregistered or delinked".to_owned(),
Self::ServiceIntentionallyDisconnected => {
"Chat service explicitly disconnected".to_owned()
}
}
}

Expand All @@ -567,6 +571,9 @@ impl FfiError for ChatServiceError {
Self::ServiceInactive => SignalErrorCode::ChatServiceInactive,
Self::AppExpired => SignalErrorCode::AppExpired,
Self::DeviceDeregistered => SignalErrorCode::DeviceDeregistered,
Self::ServiceIntentionallyDisconnected => {
SignalErrorCode::ChatServiceIntentionallyDisconnected
}
}
}
}
Expand Down
Loading

0 comments on commit 40aaecb

Please sign in to comment.