0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 03:52:17 +02:00

Remove enclave operation timeout arguments

The enclave interactions have internal progress monitoring in the form of 
websocket PING/PONG frames, so the timeout parameters aren't necessary for 
broken connection detection.
This commit is contained in:
Alex Konradi 2024-03-29 18:13:40 -04:00 committed by GitHub
parent 819606dab9
commit 10a6d8b744
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 179 additions and 308 deletions

View File

@ -5,10 +5,7 @@
package org.signal.libsignal.net;
import static org.signal.libsignal.net.DurationExt.timeoutMillis;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import org.signal.libsignal.internal.CompletableFuture;
import org.signal.libsignal.internal.Native;
@ -16,11 +13,7 @@ import org.signal.libsignal.internal.NativeHandleGuard;
class CdsiLookup implements NativeHandleGuard.Owner {
public static CompletableFuture<CdsiLookup> start(
Network network,
String username,
String password,
CdsiLookupRequest request,
Duration timeout)
Network network, String username, String password, CdsiLookupRequest request)
throws IOException, InterruptedException, ExecutionException {
CdsiLookupRequest.NativeRequest nativeRequest = request.makeNative();
@ -33,8 +26,7 @@ class CdsiLookup implements NativeHandleGuard.Owner {
connectionManager.nativeHandle(),
username,
password,
nativeRequest.getHandle(),
timeoutMillis(timeout))
nativeRequest.getHandle())
.thenApply((Long nativeHandle) -> new CdsiLookup(nativeHandle, network));
}
}

View File

@ -6,7 +6,6 @@
package org.signal.libsignal.net;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.signal.libsignal.internal.CompletableFuture;
@ -47,13 +46,9 @@ public class Network {
}
public CompletableFuture<CdsiLookupResponse> cdsiLookup(
String username,
String password,
CdsiLookupRequest request,
Duration timeout,
Consumer<byte[]> tokenConsumer)
String username, String password, CdsiLookupRequest request, Consumer<byte[]> tokenConsumer)
throws IOException, InterruptedException, ExecutionException {
return CdsiLookup.start(this, username, password, request, timeout)
return CdsiLookup.start(this, username, password, request)
.thenCompose(
(CdsiLookup lookup) -> {
tokenConsumer.accept(lookup.getToken());

View File

@ -5,9 +5,6 @@
package org.signal.libsignal.net;
import static org.signal.libsignal.net.DurationExt.timeoutMillis;
import java.time.Duration;
import org.signal.libsignal.internal.CompletableFuture;
import org.signal.libsignal.internal.Native;
import org.signal.libsignal.internal.NativeHandleGuard;
@ -26,8 +23,8 @@ import org.signal.libsignal.internal.NativeHandleGuard;
* // Instantiate EnclaveAuth with the username and password obtained from the Chat Server.
* EnclaveAuth auth = new EnclaveAuth(USERNAME, ENCLAVE_PASSWORD);
* // Store a value in SVR3. Here 10 is the number of permitted restore attempts.
* byte[] shareSet = net.svr3().backup(SECRET_TO_BE_STORED, PASSWORD, 10, auth, TIMEOUT).get();
* byte[] restoredSecret = net.svr3().restore(PASSWORD, shareSet, auth, TIMEOUT).get();
* byte[] shareSet = net.svr3().backup(SECRET_TO_BE_STORED, PASSWORD, 10, auth).get();
* byte[] restoredSecret = net.svr3().restore(PASSWORD, shareSet, auth).get();
* }</pre>
*
* <p>Please note that the methods of this class return {@link
@ -62,8 +59,6 @@ public final class Svr3 {
* and password obtained from the Chat Server. The password is an OTP which is generally good
* for about 15 minutes, therefore it can be reused for the subsequent calls to either backup
* or restore that are not too far apart in time.
* @param timeout The maximum wall time libsignal is allowed to spend communicating with SVR3
* service.
* @return an instance of {@link org.signal.libsignal.internal.CompletableFuture} which-when
* awaited-will return a byte array with a serialized masked share set. It is supposed to be
* an opaque blob for the clients and therefore no assumptions should be made about its
@ -71,14 +66,14 @@ public final class Svr3 {
* along with the password. Please note that masked share set does not have to be treated as
* secret.
* @throws {@link org.signal.libsignal.net.NetworkException} in case of network related errors,
* including timeouts and failed auth.
* including connect timeout and failed auth.
* @throws {@link org.signal.libsignal.attest.AttestationFailedException} when an attempt to
* validate the server attestation document fails.
* @throws {@link org.signal.libsignal.sgxsession.SgxCommunicationFailureException} when a Noise
* connection error happens.
*/
public final CompletableFuture<byte[]> backup(
byte[] what, String password, int maxTries, EnclaveAuth auth, Duration timeout) {
byte[] what, String password, int maxTries, EnclaveAuth auth) {
try (NativeHandleGuard asyncRuntime = new NativeHandleGuard(this.network.getAsyncContext());
NativeHandleGuard connectionManager =
new NativeHandleGuard(this.network.getConnectionManager())) {
@ -90,8 +85,7 @@ public final class Svr3 {
password,
maxTries,
auth.username,
auth.password,
timeoutMillis(timeout));
auth.password);
}
}
@ -117,12 +111,10 @@ public final class Svr3 {
* and password obtained from the Chat Server. The password is an OTP which is generally good
* for about 15 minutes, therefore it can be reused for the subsequent calls to either backup
* or restore that are not too far apart in time.
* @param timeout The maximum wall time libsignal is allowed to spend communicating with SVR3
* service.
* @return an instance of {@link org.signal.libsignal.internal.CompletableFuture} which-when
* awaited-will return a byte array with the restored secret.
* @throws {@link org.signal.libsignal.net.NetworkException} in case of network related errors,
* including timeouts and failed auth.
* including connection timeouts and failed auth.
* @throws {@link org.signal.libsignal.svr.DataMissingException} when the maximum restore attempts
* number has been exceeded or if the value has never been backed up.
* @throws {@link org.signal.libsignal.svr.RestoreFailedException} when the combination of the
@ -136,7 +128,7 @@ public final class Svr3 {
* connection error happens.
*/
public final CompletableFuture<byte[]> restore(
String password, byte[] shareSet, EnclaveAuth auth, Duration timeout) {
String password, byte[] shareSet, EnclaveAuth auth) {
try (NativeHandleGuard asyncRuntime = new NativeHandleGuard(this.network.getAsyncContext());
NativeHandleGuard connectionManager =
new NativeHandleGuard(this.network.getConnectionManager())) {
@ -147,8 +139,7 @@ public final class Svr3 {
password,
shareSet,
auth.username,
auth.password,
timeoutMillis(timeout));
auth.password);
}
}
}

View File

@ -80,7 +80,7 @@ public class CdsiLookupResponseTest {
assertLookupErrorIs("ConnectDnsFailed", IOException.class, "DNS lookup failed");
assertLookupErrorIs(
"WebSocketIdleTooLong", NetworkException.class, "channel was idle for too long");
assertLookupErrorIs("Timeout", NetworkException.class, "timeout");
assertLookupErrorIs("ConnectionTimedOut", NetworkException.class, "connect timed out");
assertLookupErrorIs("ServerCrashed", CdsiProtocolException.class, "Server error: crashed");
}
@ -88,7 +88,9 @@ public class CdsiLookupResponseTest {
String errorDescription, Class<E> expectedErrorType, String expectedMessage) {
E e =
assertThrows(
expectedErrorType, () -> Native.TESTING_CdsiLookupErrorConvert(errorDescription));
"for " + errorDescription,
expectedErrorType,
() -> Native.TESTING_CdsiLookupErrorConvert(errorDescription));
assertEquals(e.getMessage(), expectedMessage);
return e;
}

View File

@ -8,7 +8,6 @@ package org.signal.libsignal.net;
import static org.junit.Assert.*;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import org.junit.Assume;
import org.junit.Before;
@ -25,7 +24,6 @@ public class Svr3Test {
Hex.fromStringCondensedAssert(
"d2ae1668ac8a2bfd6170498332babad7cd72b9314631559a361310eee0a8adc6");
private final String ENCLAVE_SECRET = System.getenv("ENCLAVE_SECRET");
private final Duration TIMEOUT = Duration.ofSeconds(10);
private EnclaveAuth auth;
@ -53,8 +51,8 @@ public class Svr3Test {
Network net = new Network(Network.Environment.STAGING);
byte[] restored =
net.svr3()
.backup(STORED_SECRET, "password", 2, this.auth, TIMEOUT)
.thenCompose(shareSet -> net.svr3().restore("password", shareSet, this.auth, TIMEOUT))
.backup(STORED_SECRET, "password", 2, this.auth)
.thenCompose(shareSet -> net.svr3().restore("password", shareSet, this.auth))
.get();
assertEquals(Hex.toStringCondensed(STORED_SECRET), Hex.toStringCondensed(restored));
}
@ -63,11 +61,11 @@ public class Svr3Test {
public void noMoreTries() throws Exception {
Network net = new Network(Network.Environment.STAGING);
// Backup and first restore should succeed
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth, TIMEOUT).get();
net.svr3().restore("password", shareSet, this.auth, TIMEOUT).get();
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get();
net.svr3().restore("password", shareSet, this.auth).get();
try {
// The next attempt should fail
net.svr3().restore("password", shareSet, this.auth, TIMEOUT).get();
net.svr3().restore("password", shareSet, this.auth).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
assertTrue("Unexpected exception: " + cause, cause instanceof DataMissingException);
@ -77,9 +75,9 @@ public class Svr3Test {
@Test
public void failedRestore() throws Exception {
Network net = new Network(Network.Environment.STAGING);
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth, TIMEOUT).get();
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get();
try {
net.svr3().restore("wrong password", shareSet, this.auth, TIMEOUT).get();
net.svr3().restore("wrong password", shareSet, this.auth).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
assertTrue("Unexpected exception: " + cause, cause instanceof RestoreFailedException);
@ -91,14 +89,14 @@ public class Svr3Test {
Network net = new Network(Network.Environment.STAGING);
assertThrows(
IllegalArgumentException.class,
() -> net.svr3().backup(STORED_SECRET, "password", 0, this.auth, TIMEOUT).get());
() -> net.svr3().backup(STORED_SECRET, "password", 0, this.auth).get());
}
@Test
public void badSecret() throws Exception {
Network net = new Network(Network.Environment.STAGING);
try {
net.svr3().backup(new byte[31], "password", 1, this.auth, TIMEOUT).get();
net.svr3().backup(new byte[31], "password", 1, this.auth).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
assertTrue("Unexpected exception: " + cause, cause instanceof AssertionError);
@ -108,25 +106,13 @@ public class Svr3Test {
@Test
public void badShareSet() throws Exception {
Network net = new Network(Network.Environment.STAGING);
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth, TIMEOUT).get();
byte[] shareSet = net.svr3().backup(STORED_SECRET, "password", 1, this.auth).get();
shareSet[0] ^= 0xff;
try {
net.svr3().restore("password", shareSet, this.auth, TIMEOUT).get();
net.svr3().restore("password", shareSet, this.auth).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
assertTrue("Unexpected exception: " + cause, cause instanceof SvrException);
}
}
@Test
public void timeout() throws Exception {
final Duration SHORT_TIMEOUT = Duration.ofMillis(100);
Network net = new Network(Network.Environment.STAGING);
try {
net.svr3().backup(STORED_SECRET, "password", 1, this.auth, SHORT_TIMEOUT).get();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
assertTrue("Unexpected exception: " + cause, cause instanceof NetworkException);
}
}
}

View File

@ -158,7 +158,7 @@ public final class Native {
public static native void CdsiLookup_Destroy(long handle);
public static native CompletableFuture<Object> CdsiLookup_complete(long asyncRuntime, long lookup);
public static native CompletableFuture<Long> CdsiLookup_new(long asyncRuntime, long connectionManager, String username, String password, long request, int timeoutMillis);
public static native CompletableFuture<Long> CdsiLookup_new(long asyncRuntime, long connectionManager, String username, String password, long request);
public static native byte[] CdsiLookup_token(long lookup);
public static native CompletableFuture<Object> ChatService_connect_auth(long asyncRuntime, long chat);
@ -598,9 +598,9 @@ public final class Native {
public static native long Svr2Client_New(byte[] mrenclave, byte[] attestationMsg, long currentTimestamp) throws Exception;
public static native CompletableFuture<byte[]> Svr3Backup(long asyncRuntime, long connectionManager, byte[] secret, String password, int maxTries, String username, String enclavePassword, int opTimeoutMs);
public static native CompletableFuture<byte[]> Svr3Backup(long asyncRuntime, long connectionManager, byte[] secret, String password, int maxTries, String username, String enclavePassword);
public static native CompletableFuture<byte[]> Svr3Restore(long asyncRuntime, long connectionManager, String password, byte[] shareSet, String username, String enclavePassword, int opTimeoutMs);
public static native CompletableFuture<byte[]> Svr3Restore(long asyncRuntime, long connectionManager, String password, byte[] shareSet, String username, String enclavePassword);
public static native void TESTING_CdsiLookupErrorConvert(String errorDescription) throws Exception;
public static native CompletableFuture<Object> TESTING_CdsiLookupResponseConvert(long asyncRuntime);

6
node/Native.d.ts vendored
View File

@ -166,7 +166,7 @@ export function CallLinkSecretParams_DeriveFromRootKey(rootKey: Buffer): Buffer;
export function CallLinkSecretParams_GetPublicParams(paramsBytes: Buffer): Buffer;
export function Cds2ClientState_New(mrenclave: Buffer, attestationMsg: Buffer, currentTimestamp: Timestamp): SgxClientState;
export function CdsiLookup_complete(asyncRuntime: Wrapper<TokioAsyncContext>, lookup: Wrapper<CdsiLookup>): Promise<LookupResponse>;
export function CdsiLookup_new(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, username: string, password: string, request: Wrapper<LookupRequest>, timeoutMillis: number): Promise<CdsiLookup>;
export function CdsiLookup_new(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, username: string, password: string, request: Wrapper<LookupRequest>): Promise<CdsiLookup>;
export function CdsiLookup_token(lookup: Wrapper<CdsiLookup>): Buffer;
export function ChatService_connect_auth(asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<Chat>): Promise<DebugInfo>;
export function ChatService_connect_unauth(asyncRuntime: Wrapper<TokioAsyncContext>, chat: Wrapper<Chat>): Promise<DebugInfo>;
@ -461,8 +461,8 @@ export function SignedPreKeyRecord_GetSignature(obj: Wrapper<SignedPreKeyRecord>
export function SignedPreKeyRecord_GetTimestamp(obj: Wrapper<SignedPreKeyRecord>): Timestamp;
export function SignedPreKeyRecord_New(id: number, timestamp: Timestamp, pubKey: Wrapper<PublicKey>, privKey: Wrapper<PrivateKey>, signature: Buffer): SignedPreKeyRecord;
export function SignedPreKeyRecord_Serialize(obj: Wrapper<SignedPreKeyRecord>): Buffer;
export function Svr3Backup(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, secret: Buffer, password: string, maxTries: number, username: string, enclavePassword: string, opTimeoutMs: number): Promise<Buffer>;
export function Svr3Restore(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, password: string, shareSet: Buffer, username: string, enclavePassword: string, opTimeoutMs: number): Promise<Buffer>;
export function Svr3Backup(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, secret: Buffer, password: string, maxTries: number, username: string, enclavePassword: string): Promise<Buffer>;
export function Svr3Restore(asyncRuntime: Wrapper<TokioAsyncContext>, connectionManager: Wrapper<ConnectionManager>, password: string, shareSet: Buffer, username: string, enclavePassword: string): Promise<Buffer>;
export function TESTING_CdsiLookupErrorConvert(errorDescription: string): void;
export function TESTING_CdsiLookupResponseConvert(asyncRuntime: Wrapper<TokioAsyncContext>): Promise<LookupResponse>;
export function TESTING_ChatRequestGetBody(request: Wrapper<HttpRequest>): Buffer | null;

View File

@ -29,7 +29,6 @@ export type ServiceAuth = {
export type CDSRequestOptionsType = {
e164s: Array<string>;
acisAndAccessKeys: Array<{ aci: string; accessKey: string }>;
timeout: number;
returnAcisWithoutUaks: boolean;
};
@ -140,7 +139,6 @@ export class Net {
{
e164s,
acisAndAccessKeys,
timeout,
returnAcisWithoutUaks,
}: ReadonlyDeep<CDSRequestOptionsType>
): Promise<CDSResponseType<string, string>> {
@ -167,8 +165,7 @@ export class Net {
this._connectionManager,
username,
password,
request,
timeout
request
);
return await Native.CdsiLookup_complete(this._asyncContext, {
@ -193,8 +190,8 @@ export class Net {
* // Instantiate ServiceAuth with the username and password obtained from the Chat Server.
* const auth = { username: USERNAME, password: ENCLAVE_PASSWORD };
* // Store a value in SVR3. Here 10 is the number of permitted restore attempts.
* const shareSet = await SVR3.backup(SECRET_TO_BE_STORED, PASSWORD, 10, auth, TIMEOUT);
* const restoredSecret = await SVR3.restore( PASSWORD, shareSet, auth, TIMEOUT);
* const shareSet = await SVR3.backup(SECRET_TO_BE_STORED, PASSWORD, 10, auth);
* const restoredSecret = await SVR3.restore( PASSWORD, shareSet, auth);
* ```
*/
export interface Svr3Client {
@ -215,8 +212,6 @@ export interface Svr3Client {
* generally good for about 15 minutes, therefore it can be reused for the
* subsequent calls to either backup or restore that are not too far apart in
* time.
* @param opTimeoutMs - The maximum wall time libsignal is allowed to spend
* communicating with SVR3 service.
* @returns A `Promise` which--when awaited--will return a byte array with a
* serialized masked share set. It is supposed to be an opaque blob for the
* clients and therefore no assumptions should be made about its contents.
@ -224,20 +219,20 @@ export interface Svr3Client {
* secret along with the password. Please note that masked share set does not
* have to be treated as secret.
*
* The returned `Promise` can also fail due to the network issues (including the
* timeout), problems establishing the Noise connection to the enclaves, or
* invalid arguments' values. {@link IoError} errors can, in general, be
* retried, although there is already a retry-with-backoff mechanism inside
* libsignal used to connect to the SVR3 servers. Other exceptions are caused
* by the bad input or data missing on the server. They are therefore
* non-actionable and are guaranteed to be thrown again when retried.
* The returned `Promise` can also fail due to the network issues (including a
* connection timeout), problems establishing the Noise connection to the
* enclaves, or invalid arguments' values. {@link IoError} errors can, in
* general, be retried, although there is already a retry-with-backoff
* mechanism inside libsignal used to connect to the SVR3 servers. Other
* exceptions are caused by the bad input or data missing on the server. They
* are therefore non-actionable and are guaranteed to be thrown again when
* retried.
*/
backup(
what: Buffer,
password: string,
maxTries: number,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
auth: Readonly<ServiceAuth>
): Promise<Buffer>;
/**
@ -255,18 +250,17 @@ export interface Svr3Client {
* generally good for about 15 minutes, therefore it can be reused for the
* subsequent calls to either backup or restore that are not too far apart in
* time.
* @param opTimeoutMs - The maximum wall time libsignal is allowed to spend
* communicating with SVR3 service.
* @returns A `Promise` which--when awaited--will return a byte array with the
* restored secret.
*
* The returned `Promise` can also fail due to the network issues (including the
* timeout), problems establishing the Noise connection to the enclaves, or
* invalid arguments' values. {@link IoError} errors can, in general, be
* retried, although there is already a retry-with-backoff mechanism inside
* libsignal used to connect to the SVR3 servers. Other exceptions are caused
* by the bad input or data missing on the server. They are therefore
* non-actionable and are guaranteed to be thrown again when retried.
* The returned `Promise` can also fail due to the network issues (including
* the connection timeout), problems establishing the Noise connection to the
* enclaves, or invalid arguments' values. {@link IoError} errors can, in
* general, be retried, although there is already a retry-with-backoff
* mechanism inside libsignal used to connect to the SVR3 servers. Other
* exceptions are caused by the bad input or data missing on the server. They
* are therefore non-actionable and are guaranteed to be thrown again when
* retried.
*
* - {@link SvrDataMissingError} is returned when the maximum restore attempts
* number has been exceeded or if the value has never been backed up.
@ -280,8 +274,7 @@ export interface Svr3Client {
restore(
password: string,
shareSet: Buffer,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
auth: Readonly<ServiceAuth>
): Promise<Buffer>;
}
@ -297,8 +290,7 @@ class Svr3ClientImpl implements Svr3Client {
what: Buffer,
password: string,
maxTries: number,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
auth: Readonly<ServiceAuth>
): Promise<Buffer> {
return Native.Svr3Backup(
this._asyncContext,
@ -307,16 +299,14 @@ class Svr3ClientImpl implements Svr3Client {
password,
maxTries,
auth.username,
auth.password,
opTimeoutMs
auth.password
);
}
async restore(
password: string,
shareSet: Buffer,
auth: Readonly<ServiceAuth>,
opTimeoutMs: number
auth: Readonly<ServiceAuth>
): Promise<Buffer> {
return Native.Svr3Restore(
this._asyncContext,
@ -324,8 +314,7 @@ class Svr3ClientImpl implements Svr3Client {
password,
shareSet,
auth.username,
auth.password,
opTimeoutMs
auth.password
);
}
}

View File

@ -196,7 +196,7 @@ describe('cdsi lookup', () => {
ErrorCode.IoError,
'websocket error: channel was idle for too long',
],
['Timeout', ErrorCode.IoError, 'lookup timed out'],
['ConnectionTimedOut', ErrorCode.IoError, 'connect attempt timed out'],
['ServerCrashed', ErrorCode.IoError, 'server error: crashed'],
];
cases.forEach((testCase) => {
@ -213,7 +213,6 @@ describe('cdsi lookup', () => {
});
describe('SVR3', () => {
const TIMEOUT = 5000;
const USERNAME = randomBytes(16).toString('hex');
const SVR3 = new Net(Environment.Staging).svr3;
@ -232,14 +231,14 @@ describe('SVR3', () => {
it('maxTries must be positive', () => {
const secret = randomBytes(32);
return expect(SVR3.backup(secret, 'password', 0, AUTH, TIMEOUT)).to
.eventually.be.rejected;
return expect(SVR3.backup(secret, 'password', 0, AUTH)).to.eventually.be
.rejected;
});
it('Secret must be 32 bytes', () => {
const secret = randomBytes(42);
return expect(SVR3.backup(secret, 'password', 1, AUTH, TIMEOUT)).to
.eventually.be.rejected;
return expect(SVR3.backup(secret, 'password', 1, AUTH)).to.eventually.be
.rejected;
});
});
@ -248,7 +247,7 @@ describe('SVR3', () => {
const auth = make_auth();
const shareSet = Buffer.alloc(0);
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
SVR3.restore('password', shareSet, auth)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
});
@ -256,7 +255,7 @@ describe('SVR3', () => {
const auth = make_auth();
const shareSet = Buffer.from([42]);
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
SVR3.restore('password', shareSet, auth)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
});
});
@ -274,21 +273,16 @@ describe('SVR3', () => {
it('Backup and restore work in staging', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
const restoredSecret = await SVR3.restore(
'password',
shareSet,
auth,
TIMEOUT
);
const shareSet = await SVR3.backup(secret, 'password', 10, auth);
const restoredSecret = await SVR3.restore('password', shareSet, auth);
expect(restoredSecret).to.eql(secret);
}).timeout(10000);
it('Restore with wrong password', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
return expect(SVR3.restore('wrong password', shareSet, auth, TIMEOUT))
const shareSet = await SVR3.backup(secret, 'password', 10, auth);
return expect(SVR3.restore('wrong password', shareSet, auth))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.SvrRestoreFailed);
}).timeout(10000);
@ -296,33 +290,24 @@ describe('SVR3', () => {
it('Restore with corrupted share set', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 10, auth, TIMEOUT);
const shareSet = await SVR3.backup(secret, 'password', 10, auth);
// The first byte is the serialization format version, changing that
// _will_ fail (checked in the other test). Changing the actual share set
// value makes a more interesting test case.
shareSet[1] ^= 0xff;
return expect(
SVR3.restore('password', shareSet, auth, TIMEOUT)
SVR3.restore('password', shareSet, auth)
).to.eventually.be.rejectedWith(LibSignalErrorBase);
}).timeout(10000);
it('Exceed maxTries', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const shareSet = await SVR3.backup(secret, 'password', 1, auth, TIMEOUT);
await SVR3.restore('password', shareSet, auth, TIMEOUT);
return expect(SVR3.restore('password', shareSet, auth, TIMEOUT))
const shareSet = await SVR3.backup(secret, 'password', 1, auth);
await SVR3.restore('password', shareSet, auth);
return expect(SVR3.restore('password', shareSet, auth))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.SvrDataMissing);
});
it('Timeout', async () => {
const auth = make_auth();
const secret = randomBytes(32);
const SHORT_TIMEOUT = 100;
return expect(SVR3.backup(secret, 'password', 10, auth, SHORT_TIMEOUT))
.to.eventually.be.rejectedWith(LibSignalErrorBase)
.and.have.property('code', ErrorCode.IoError);
});
});
});

View File

@ -79,7 +79,7 @@ pub enum SignalErrorCode {
#[allow(dead_code)]
UnsupportedMediaInput = 132,
Timeout = 133,
ConnectionTimedOut = 133,
NetworkProtocol = 134,
RateLimited = 135,
WebSocket = 136,
@ -320,7 +320,7 @@ impl From<&SignalFfiError> for SignalErrorCode {
}
}
SignalFfiError::WebSocket(_) => SignalErrorCode::WebSocket,
SignalFfiError::Timeout => SignalErrorCode::Timeout,
SignalFfiError::ConnectionTimedOut => SignalErrorCode::ConnectionTimedOut,
SignalFfiError::NetworkProtocol(_) => SignalErrorCode::NetworkProtocol,
SignalFfiError::CdsiInvalidToken => SignalErrorCode::CdsiInvalidToken,
SignalFfiError::RateLimited {

View File

@ -37,7 +37,7 @@ pub enum SignalFfiError {
UsernameLinkError(UsernameLinkError),
Io(IoError),
WebSocket(#[from] WebSocketServiceError),
Timeout,
ConnectionTimedOut,
NetworkProtocol(String),
CdsiInvalidToken,
RateLimited {
@ -76,7 +76,7 @@ impl fmt::Display for SignalFfiError {
SignalFfiError::UsernameProofError(e) => write!(f, "{}", e),
SignalFfiError::UsernameLinkError(e) => write!(f, "{}", e),
SignalFfiError::Io(e) => write!(f, "IO error: {}", e),
SignalFfiError::Timeout => write!(f, "Operation timed out"),
SignalFfiError::ConnectionTimedOut => write!(f, "Connect timed out"),
SignalFfiError::WebSocket(e) => write!(f, "WebSocket error: {e}"),
SignalFfiError::CdsiInvalidToken => write!(f, "CDSI request token was invalid"),
SignalFfiError::NetworkProtocol(message) => write!(f, "Protocol error: {}", message),
@ -199,7 +199,7 @@ impl From<libsignal_net::cdsi::LookupError> for SignalFfiError {
LookupError::AttestationError(e) => SignalFfiError::Sgx(e),
LookupError::ConnectTransport(e) => SignalFfiError::Io(e.into()),
LookupError::WebSocket(e) => SignalFfiError::WebSocket(e),
LookupError::Timeout => SignalFfiError::Timeout,
LookupError::ConnectionTimedOut => SignalFfiError::ConnectionTimedOut,
LookupError::ParseError
| LookupError::Protocol
| LookupError::InvalidResponse
@ -224,11 +224,11 @@ impl From<Svr3Error> for SignalFfiError {
match err {
Svr3Error::Connect(e) => match e {
WebSocketConnectError::Transport(e) => SignalFfiError::Io(e.into()),
WebSocketConnectError::Timeout => SignalFfiError::Timeout,
WebSocketConnectError::Timeout => SignalFfiError::ConnectionTimedOut,
WebSocketConnectError::WebSocketError(e) => WebSocketServiceError::from(e).into(),
},
Svr3Error::Service(e) => SignalFfiError::WebSocket(e),
Svr3Error::Timeout => SignalFfiError::Timeout,
Svr3Error::ConnectionTimedOut => SignalFfiError::ConnectionTimedOut,
Svr3Error::AttestationError(inner) => SignalFfiError::Sgx(inner),
Svr3Error::Protocol(inner) => SignalFfiError::NetworkProtocol(inner.to_string()),
Svr3Error::RequestFailed(_) | Svr3Error::RestoreFailed | Svr3Error::DataMissing => {

View File

@ -49,7 +49,7 @@ pub enum SignalJniError {
WebSocket(#[from] WebSocketServiceError),
ChatService(ChatServiceError),
InvalidUri(InvalidUri),
Timeout,
ConnectTimedOut,
Bridge(BridgeLayerError),
#[cfg(feature = "testing-fns")]
TestingError {
@ -97,7 +97,7 @@ impl fmt::Display for SignalJniError {
SignalJniError::ChatService(e) => write!(f, "{}", e),
SignalJniError::InvalidUri(e) => write!(f, "{}", e),
SignalJniError::WebSocket(e) => write!(f, "{e}"),
SignalJniError::Timeout => write!(f, "timeout"),
SignalJniError::ConnectTimedOut => write!(f, "connect timed out"),
SignalJniError::Svr3(e) => write!(f, "{}", e),
SignalJniError::Bridge(e) => write!(f, "{}", e),
#[cfg(feature = "testing-fns")]
@ -251,7 +251,7 @@ impl From<libsignal_net::cdsi::LookupError> for SignalJniError {
fn from(e: libsignal_net::cdsi::LookupError) -> SignalJniError {
use libsignal_net::cdsi::LookupError;
SignalJniError::Cdsi(match e {
LookupError::Timeout => return SignalJniError::Timeout,
LookupError::ConnectionTimedOut => return SignalJniError::ConnectTimedOut,
LookupError::AttestationError(e) => return e.into(),
LookupError::ConnectTransport(e) => return IoError::from(e).into(),
LookupError::WebSocket(e) => return e.into(),
@ -290,11 +290,11 @@ impl From<Svr3Error> for SignalJniError {
fn from(err: Svr3Error) -> Self {
match err {
Svr3Error::Connect(inner) => match inner {
WebSocketConnectError::Timeout => SignalJniError::Timeout,
WebSocketConnectError::Timeout => SignalJniError::ConnectTimedOut,
WebSocketConnectError::Transport(e) => SignalJniError::Io(e.into()),
WebSocketConnectError::WebSocketError(e) => WebSocketServiceError::from(e).into(),
},
Svr3Error::Timeout => SignalJniError::Timeout,
Svr3Error::ConnectionTimedOut => SignalJniError::ConnectTimedOut,
Svr3Error::Service(inner) => inner.into(),
Svr3Error::AttestationError(inner) => inner.into(),
Svr3Error::Protocol(_)

View File

@ -581,7 +581,7 @@ where
jni_class_name!(org.signal.libsignal.net.CdsiProtocolException),
error,
),
SignalJniError::WebSocket(_) | SignalJniError::Timeout => (
SignalJniError::WebSocket(_) | SignalJniError::ConnectTimedOut => (
jni_class_name!(org.signal.libsignal.net.NetworkException),
error,
),

View File

@ -30,7 +30,6 @@ use libsignal_net::infra::tcp_ssl::TcpSslTransportConnector;
use libsignal_net::infra::{make_ws_config, EndpointConnection};
use libsignal_net::svr::{self, SvrConnection};
use libsignal_net::svr3::{self, OpaqueMaskedShareSet, PpssOps as _};
use libsignal_net::utils::timeout;
use libsignal_net::{chat, env};
use rand::rngs::OsRng;
use tokio::sync::mpsc;
@ -168,29 +167,24 @@ async fn Svr3Backup(
max_tries: AsType<NonZeroU32, u32>,
username: String, // hex-encoded uid
enclave_password: String, // timestamp:otp(...)
op_timeout_ms: u32, // timeout spans both connecting and performing the operation
) -> Result<Vec<u8>, svr3::Error> {
let secret = secret
.as_ref()
.try_into()
.expect("can only backup 32 bytes");
let mut rng = OsRng;
let share_set = timeout(
Duration::from_millis(op_timeout_ms.into()),
svr::Error::Timeout.into(),
svr3_connect(connection_manager, username, enclave_password)
.map_err(|err| err.into())
.and_then(|connections| {
Svr3Env::backup(
connections,
&password,
secret,
max_tries.into_inner(),
&mut rng,
)
}),
)
.await?;
let share_set = svr3_connect(connection_manager, username, enclave_password)
.map_err(|err| err.into())
.and_then(|connections| {
Svr3Env::backup(
connections,
&password,
secret,
max_tries.into_inner(),
&mut rng,
)
})
.await?;
Ok(share_set.serialize().expect("can serialize the share set"))
}
@ -201,18 +195,13 @@ async fn Svr3Restore(
share_set: Box<[u8]>,
username: String, // hex-encoded uid
enclave_password: String, // timestamp:otp(...)
op_timeout_ms: u32, // timeout spans both connecting and performing the operation
) -> Result<Vec<u8>, svr3::Error> {
let mut rng = OsRng;
let share_set = OpaqueMaskedShareSet::deserialize(&share_set)?;
let restored_secret = timeout(
Duration::from_millis(op_timeout_ms.into()),
svr::Error::Timeout.into(),
svr3_connect(connection_manager, username, enclave_password)
.map_err(|err| err.into())
.and_then(|connections| Svr3Env::restore(connections, &password, share_set, &mut rng)),
)
.await?;
let restored_secret = svr3_connect(connection_manager, username, enclave_password)
.map_err(|err| err.into())
.and_then(|connections| Svr3Env::restore(connections, &password, share_set, &mut rng))
.await?;
Ok(restored_secret.to_vec())
}

View File

@ -4,14 +4,12 @@
//
use std::convert::TryInto as _;
use std::time::Duration;
use libsignal_bridge_macros::{bridge_fn, bridge_io};
use libsignal_net::auth::Auth;
use libsignal_net::cdsi::{
self, AciAndAccessKey, CdsiConnection, ClientResponseCollector, LookupResponse, Token, E164,
};
use libsignal_net::utils::timeout;
use libsignal_protocol::{Aci, SignalProtocolError};
use crate::net::{ConnectionManager, TokioAsyncContext};
@ -29,7 +27,7 @@ pub enum CdsiError {
/// Invalid response received from the server
InvalidResponse,
/// Retry later
RateLimited { retry_after: Duration },
RateLimited { retry_after: std::time::Duration },
/// Failed to parse the response from the server
ParseError,
/// Request token was invalid
@ -109,7 +107,6 @@ async fn CdsiLookup_new(
username: String,
password: String,
request: &LookupRequest,
timeout_millis: u32,
) -> Result<CdsiLookup, cdsi::LookupError> {
let request = std::mem::take(&mut *request.0.lock().expect("not poisoned"));
let auth = Auth { username, password };
@ -120,12 +117,7 @@ async fn CdsiLookup_new(
auth,
)
.await?;
let (token, remaining_response) = timeout(
Duration::from_millis(timeout_millis.into()),
cdsi::LookupError::Timeout,
connected.send_request(request),
)
.await?;
let (token, remaining_response) = connected.send_request(request).await?;
Ok(CdsiLookup {
token,

View File

@ -461,7 +461,7 @@ impl SignalNodeError for libsignal_net::cdsi::LookupError {
Self::AttestationError(e) => return e.throw(cx, module, operation_name),
Self::InvalidArgument { server_reason: _ } => (None, None),
Self::InvalidToken => (Some("CdsiInvalidToken"), None),
Self::Timeout
Self::ConnectionTimedOut
| Self::ConnectTransport(_)
| Self::WebSocket(_)
| Self::Protocol
@ -485,7 +485,9 @@ impl SignalNodeError for libsignal_net::svr3::Error {
operation_name: &str,
) -> JsResult<'a, JsValue> {
let name = match self {
Svr3Error::Service(_) | Svr3Error::Timeout | Svr3Error::Connect(_) => Some(IO_ERROR),
Svr3Error::Service(_) | Svr3Error::ConnectionTimedOut | Svr3Error::Connect(_) => {
Some(IO_ERROR)
}
Svr3Error::AttestationError(inner) => {
return inner.throw(cx, module, operation_name);
}

View File

@ -59,7 +59,7 @@ enum TestingCdsiLookupError {
Parse,
ConnectDnsFailed,
WebSocketIdleTooLong,
Timeout,
ConnectionTimedOut,
ServerCrashed,
}
@ -83,7 +83,7 @@ const _: () = {
LookupError::ParseError => TestingCdsiLookupError::Parse,
LookupError::ConnectTransport(_) => TestingCdsiLookupError::ConnectDnsFailed,
LookupError::WebSocket(_) => TestingCdsiLookupError::WebSocketIdleTooLong,
LookupError::Timeout => TestingCdsiLookupError::Timeout,
LookupError::ConnectionTimedOut => TestingCdsiLookupError::ConnectionTimedOut,
LookupError::Server { reason } => TestingCdsiLookupError::ServerCrashed,
}
}
@ -124,7 +124,7 @@ fn TESTING_CdsiLookupErrorConvert(
TestingCdsiLookupError::WebSocketIdleTooLong => LookupError::WebSocket(
libsignal_net::infra::ws::WebSocketServiceError::ChannelIdleTooLong,
),
TestingCdsiLookupError::Timeout => LookupError::Timeout,
TestingCdsiLookupError::ConnectionTimedOut => LookupError::ConnectionTimedOut,
TestingCdsiLookupError::ServerCrashed => LookupError::Server { reason: "crashed" },
})
}

View File

@ -26,7 +26,7 @@ async fn cdsi_lookup(
let connected = CdsiConnection::connect(endpoint, transport_connector, auth).await?;
let (_token, remaining_response) = libsignal_net::utils::timeout(
timeout,
LookupError::Timeout,
LookupError::ConnectionTimedOut,
connected.send_request(request),
)
.await?;

View File

@ -285,8 +285,8 @@ pub enum LookupError {
ConnectTransport(TransportConnectError),
/// websocket error: {0}
WebSocket(WebSocketServiceError),
/// lookup timed out
Timeout,
/// connect attempt timed out
ConnectionTimedOut,
/// request was invalid: {server_reason}
InvalidArgument { server_reason: String },
/// server error: {reason}
@ -306,9 +306,10 @@ impl From<AttestedConnectionError> for LookupError {
impl From<crate::enclave::Error> for LookupError {
fn from(value: crate::enclave::Error) -> Self {
use crate::enclave::Error;
match value {
crate::svr::Error::WebSocketConnect(err) => match err {
WebSocketConnectError::Timeout => Self::Timeout,
Error::WebSocketConnect(err) => match err {
WebSocketConnectError::Timeout => Self::ConnectionTimedOut,
WebSocketConnectError::Transport(e) => Self::ConnectTransport(e),
WebSocketConnectError::WebSocketError(e) => {
if let tungstenite::Error::Http(response) = &e {
@ -328,10 +329,10 @@ impl From<crate::enclave::Error> for LookupError {
Self::WebSocket(e.into())
}
},
crate::svr::Error::AttestationError(err) => Self::AttestationError(err),
crate::svr::Error::WebSocket(err) => Self::WebSocket(err),
crate::svr::Error::Protocol => Self::Protocol,
crate::svr::Error::Timeout => Self::Timeout,
Error::AttestationError(err) => Self::AttestationError(err),
Error::WebSocket(err) => Self::WebSocket(err),
Error::Protocol => Self::Protocol,
Error::ConnectionTimedOut => Self::ConnectionTimedOut,
}
}
}

View File

@ -214,8 +214,8 @@ pub enum Error {
Protocol,
/// Enclave attestation failed: {0}
AttestationError(attest::enclave::Error),
/// Timeout
Timeout,
/// Connection timeout
ConnectionTimedOut,
}
impl LogSafeDisplay for Error {}
@ -257,7 +257,7 @@ impl<E: EnclaveKind + NewHandshake, C: ConnectionManager> EnclaveEndpointConnect
unreachable!("new service connector should not be in cooldown")
}
ServiceState::Error(e) => Err(Error::WebSocketConnect(e)),
ServiceState::TimedOut => Err(Error::Timeout),
ServiceState::ConnectionTimedOut => Err(Error::ConnectionTimedOut),
ServiceState::Inactive => {
unreachable!("can't be returned by the initializer")
}

View File

@ -36,7 +36,7 @@ pub(crate) enum ServiceState<T, CE, SE> {
/// Last connection attempt resulted in an error.
Error(CE),
/// Last connection attempt timed out.
TimedOut,
ConnectionTimedOut,
}
/// Represents the logic needed to establish a connection over some transport.
@ -217,7 +217,7 @@ where
}
ConnectionAttemptOutcome::TimedOut => {
log::debug!("connection attempt timed out");
ServiceState::TimedOut
ServiceState::ConnectionTimedOut
}
}
}
@ -263,7 +263,7 @@ where
ServiceState::Active(service, status) if !status.is_stopped() => Ok(mapper(service)),
ServiceState::Inactive => Err(StateError::Inactive),
ServiceState::Cooldown(_)
| ServiceState::TimedOut
| ServiceState::ConnectionTimedOut
| ServiceState::Error(_)
| ServiceState::Active(_, _) => Err(StateError::ServiceUnavailable),
}
@ -352,7 +352,7 @@ where
// because we just checked that we'll wake before the deadline
tokio::time::sleep_until(*next_attempt_time).await;
}
ServiceState::TimedOut => {
ServiceState::ConnectionTimedOut => {
// keep trying until we hit our own timeout deadline
log::info!("Connection attempt timed out");
if Instant::now() >= deadline {
@ -374,7 +374,7 @@ where
ServiceState::Active(service, service_state)
}
Ok(result) => result,
Err(_) => ServiceState::TimedOut,
Err(_) => ServiceState::ConnectionTimedOut,
}
}
}

View File

@ -143,8 +143,8 @@ pub enum Error {
/// This could mean either the data was never backed-up or we ran out of attempts to restore
/// it.
DataMissing,
/// Timeout
Timeout,
/// Connect timed out
ConnectionTimedOut,
}
impl From<DeserializeError> for Error {
@ -184,7 +184,7 @@ impl From<super::svr::Error> for Error {
SvrError::WebSocket(inner) => Self::Service(inner),
SvrError::Protocol => Self::Protocol("General SVR protocol error".to_string()),
SvrError::AttestationError(inner) => Self::AttestationError(inner),
SvrError::Timeout => Self::Timeout,
SvrError::ConnectionTimedOut => Self::ConnectionTimedOut,
}
}
}

View File

@ -54,7 +54,7 @@ public enum SignalError: Error {
case unsupportedMediaInput(String)
case callbackError(String)
case webSocketError(String)
case timeoutError(String)
case connectionTimeoutError(String)
case networkProtocolError(String)
case cdsiInvalidToken(String)
case rateLimitedError(retryAfter: TimeInterval, message: String)
@ -168,8 +168,8 @@ internal func checkError(_ error: SignalFfiErrorRef?) throws {
throw SignalError.callbackError(errStr)
case SignalErrorCodeWebSocket:
throw SignalError.webSocketError(errStr)
case SignalErrorCodeTimeout:
throw SignalError.timeoutError(errStr)
case SignalErrorCodeConnectionTimedOut:
throw SignalError.connectionTimeoutError(errStr)
case SignalErrorCodeNetworkProtocol:
throw SignalError.networkProtocolError(errStr)
case SignalErrorCodeCdsiInvalidToken:

View File

@ -32,18 +32,17 @@ public class Net {
self.svr3 = Svr3Client(self.asyncContext, self.connectionManager)
}
/// Like ``cdsiLookup(auth:request:timeout:)`` but with the parameters to ``CdsiLookupRequest`` broken out.
/// Like ``cdsiLookup(auth:request:)`` but with the parameters to ``CdsiLookupRequest`` broken out.
public func cdsiLookup(
auth: Auth,
prevE164s: [String],
e164s: [String],
acisAndAccessKeys: [AciAndAccessKey],
returnAcisWithoutUaks: Bool,
token: Data?,
timeout: TimeInterval
token: Data?
) async throws -> CdsiLookup {
let request = try CdsiLookupRequest(e164s: e164s, prevE164s: prevE164s, acisAndAccessKeys: acisAndAccessKeys, token: token, returnAcisWithoutUaks: returnAcisWithoutUaks)
return try await self.cdsiLookup(auth: auth, request: request, timeout: timeout)
return try await self.cdsiLookup(auth: auth, request: request)
}
/// Starts a new CDSI lookup request.
@ -56,7 +55,6 @@ public class Net {
/// - Parameters:
/// - auth: The information to use when authenticating with the CDSI server.
/// - request: The CDSI request to be sent to the server.
/// - timeout: The amount of time to wait for the initial connection before giving up.
///
/// - Returns:
/// An object representing the in-progress request. If this method
@ -77,7 +75,7 @@ public class Net {
///
/// // Start the request.
/// let net = Net(env: Net.Environment.production)
/// let lookup = try await net.cdsiLookup(auth: auth, request: request, timeout: TimeInterval(10))
/// let lookup = try await net.cdsiLookup(auth: auth, request: request)
///
/// // Save the token for future lookups.
/// let savedToken = lookup.token
@ -90,15 +88,13 @@ public class Net {
/// ```
public func cdsiLookup(
auth: Auth,
request: CdsiLookupRequest,
timeout: TimeInterval
request: CdsiLookupRequest
) async throws -> CdsiLookup {
let timeoutMs = durationToMillis(timeout)
let handle: OpaquePointer = try await invokeAsyncFunction { promise, context in
self.asyncContext.withNativeHandle { asyncContext in
self.connectionManager.withNativeHandle { connectionManager in
request.withNativeHandle { request in
signal_cdsi_lookup_new(promise, context, asyncContext, connectionManager, auth.username, auth.password, request, timeoutMs)
signal_cdsi_lookup_new(promise, context, asyncContext, connectionManager, auth.username, auth.password, request)
}
}
}
@ -204,7 +200,7 @@ public class CdsiLookupRequest: NativeHandleOwner {
/// CDSI lookup in progress.
///
/// Returned by ``Net/cdsiLookup(auth:request:timeout:)`` when a request is successfully initiated.
/// Returned by ``Net/cdsiLookup(auth:request:)`` when a request is successfully initiated.
public class CdsiLookup {
class NativeCdsiLookup: NativeHandleOwner {
override internal class func destroyNativeHandle(_ handle: OpaquePointer) -> SignalFfiErrorRef? {

View File

@ -25,16 +25,14 @@ import SignalFfi
/// SECRET_TO_BE_STORED,
/// password: PASSWORD,
/// maxTries: 10,
/// auth: auth,
/// timeout: TIMEOUT
/// auth: auth
/// )
/// // Attempt to retrieve the secret from SVR3 provided the masked share set
/// // and a password.
/// let restoredSecret = try await net.svr3.restore(
/// password: PASSWORD,
/// shareSet: shareSet,
/// auth: auth,
/// timeout: TIMEOUT
/// auth: auth
/// )
/// ~~~
public class Svr3Client {
@ -56,7 +54,7 @@ public class Svr3Client {
/// - password: User-provided password that will be used to derive the
/// encryption key for the secret.
/// - maxTries: Maximum allowed number of restore attempts (successful
/// or not). Each call to ``restore(password:shareSet:auth:timeout:)``
/// or not). Each call to ``restore(password:shareSet:auth:)``
/// that reaches the server will decrement the counter. Must be
/// positive.
/// - auth: An instance of ``Auth`` containing the username and password
@ -64,8 +62,6 @@ public class Svr3Client {
/// generally good for about 15 minutes, therefore it can be reused for
/// the subsequent calls to either `backup` or `restore` that are not
/// too far apart in time.
/// - timeout: The maximum wall time libsignal is allowed to spend
/// communicating with SVR3 service.
///
/// - Returns:
/// A byte array containing a serialized masked share set. It is supposed
@ -78,15 +74,15 @@ public class Svr3Client {
/// - Throws:
/// On error, throws a ``SignalError``. Expected error cases are
/// - `SignalError.networkError` for a network-level connectivity issue,
/// including timeouts.
/// including connection timeout.
/// - `SignalError.networkProtocolError` for an SVR3 or attested
/// connection protocol issue.
///
/// ## Notes:
/// - Error messages are expected to be log-safe and not contain any
/// sensitive data.
/// - Failures caused by the network issues (including the timeouts) can,
/// in general, be retried, although there is already a
/// - Failures caused by the network issues (including a connection
/// timeout) can, in general, be retried, although there is already a
/// retry-with-backoff mechanism inside libsignal used to connect to the
/// SVR3 servers. Other exceptions are caused by the bad input or data
/// missing on the server. They are therefore non-actionable and are
@ -95,10 +91,8 @@ public class Svr3Client {
_ secret: some ContiguousBytes,
password: String,
maxTries: UInt32,
auth: Auth,
timeout: TimeInterval
auth: Auth
) async throws -> [UInt8] {
let timeoutMs = durationToMillis(timeout)
let output = try await invokeAsyncFunction(returning: SignalOwnedBuffer.self) { promise, context in
self.asyncContext.withNativeHandle { asyncContext in
self.connectionManager.withNativeHandle { connectionManager in
@ -112,8 +106,7 @@ public class Svr3Client {
password,
maxTries,
auth.username,
auth.password,
timeoutMs
auth.password
)
}
}
@ -131,14 +124,12 @@ public class Svr3Client {
/// - password: User-provided password that will be used to derive the
/// encryption key for the secret.
/// - shareSet: A serialized masked share set returned by
/// ``backup(_:password:maxTries:auth:timeout:)``.
/// ``backup(_:password:maxTries:auth:)``.
/// - auth: An instance of ``Auth`` containing the username and password
/// obtained from the Chat Server. The password is an OTP which is
/// generally good for about 15 minutes, therefore it can be reused for
/// the subsequent calls to either backup or restore that are not too
/// far apart in time.
/// - timeout: The maximum wall time libsignal is allowed to spend
/// communicating with SVR3 service.
///
/// - Returns:
/// A byte array containing the restored secret.
@ -146,7 +137,7 @@ public class Svr3Client {
/// - Throws:
/// On error, throws a ``SignalError``. Expected error cases are
/// - `SignalError.networkError` for a network-level connectivity issue,
/// including timeouts.
/// including connection timeouts.
/// - `SignalError.networkProtocolError` for an SVR3 or attested
/// connection protocol issue.
/// - `SignalError.svrDataMissing` when either the maximum number of
@ -158,8 +149,8 @@ public class Svr3Client {
/// ## Notes:
/// - Error messages are expected to be log-safe and not contain any
/// sensitive data.
/// - Failures caused by the network issues (including the timeouts) can,
/// in general, be retried, although there is already a
/// - Failures caused by the network issues (including a connection
/// timeout) can, in general, be retried, although there is already a
/// retry-with-backoff mechanism inside libsignal used to connect to the
/// SVR3 servers. Other exceptions are caused by the bad input or data
/// missing on the server. They are therefore non-actionable and are
@ -167,10 +158,8 @@ public class Svr3Client {
public func restore(
password: String,
shareSet: some ContiguousBytes,
auth: Auth,
timeout: TimeInterval
auth: Auth
) async throws -> [UInt8] {
let timeoutMs = durationToMillis(timeout)
let output = try await invokeAsyncFunction(returning: SignalOwnedBuffer.self) { promise, context in
self.asyncContext.withNativeHandle { asyncContext in
self.connectionManager.withNativeHandle { connectionManager in
@ -183,8 +172,7 @@ public class Svr3Client {
password,
shareSetBuffer,
auth.username,
auth.password,
timeoutMs
auth.password
)
}
}

View File

@ -184,7 +184,7 @@ typedef enum {
SignalErrorCodeIoError = 130,
SignalErrorCodeInvalidMediaInput = 131,
SignalErrorCodeUnsupportedMediaInput = 132,
SignalErrorCodeTimeout = 133,
SignalErrorCodeConnectionTimedOut = 133,
SignalErrorCodeNetworkProtocol = 134,
SignalErrorCodeRateLimited = 135,
SignalErrorCodeWebSocket = 136,
@ -1329,9 +1329,9 @@ SignalFfiError *signal_create_otp(const char **out, const char *username, Signal
SignalFfiError *signal_create_otp_from_base64(const char **out, const char *username, const char *secret);
SignalFfiError *signal_svr3_backup(SignalCPromiseOwnedBufferOfc_uchar promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, SignalBorrowedBuffer secret, const char *password, uint32_t max_tries, const char *username, const char *enclave_password, uint32_t op_timeout_ms);
SignalFfiError *signal_svr3_backup(SignalCPromiseOwnedBufferOfc_uchar promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, SignalBorrowedBuffer secret, const char *password, uint32_t max_tries, const char *username, const char *enclave_password);
SignalFfiError *signal_svr3_restore(SignalCPromiseOwnedBufferOfc_uchar promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, const char *password, SignalBorrowedBuffer share_set, const char *username, const char *enclave_password, uint32_t op_timeout_ms);
SignalFfiError *signal_svr3_restore(SignalCPromiseOwnedBufferOfc_uchar promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, const char *password, SignalBorrowedBuffer share_set, const char *username, const char *enclave_password);
SignalFfiError *signal_chat_destroy(SignalChat *p);
@ -1353,7 +1353,7 @@ SignalFfiError *signal_lookup_request_destroy(SignalLookupRequest *p);
SignalFfiError *signal_cdsi_lookup_destroy(SignalCdsiLookup *p);
SignalFfiError *signal_cdsi_lookup_new(SignalCPromiseCdsiLookup promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, const char *username, const char *password, const SignalLookupRequest *request, uint32_t timeout_millis);
SignalFfiError *signal_cdsi_lookup_new(SignalCPromiseCdsiLookup promise, const void *promise_context, const SignalTokioAsyncContext *async_runtime, const SignalConnectionManager *connection_manager, const char *username, const char *password, const SignalLookupRequest *request);
SignalFfiError *signal_cdsi_lookup_token(SignalOwnedBuffer *out, const SignalCdsiLookup *lookup);

View File

@ -92,9 +92,9 @@ final class NetTests: XCTestCase {
XCTAssertEqual(message, "WebSocket error: channel was idle for too long")
}
do {
try failWithError("Timeout")
} catch SignalError.timeoutError(let message) {
XCTAssertEqual(message, "Operation timed out")
try failWithError("ConnectionTimedOut")
} catch SignalError.connectionTimeoutError(let message) {
XCTAssertEqual(message, "Connect timed out")
}
do {
try failWithError("ServerCrashed")
@ -116,7 +116,7 @@ final class NetTests: XCTestCase {
)
let net = Net(env: .staging)
let lookup = try await net.cdsiLookup(auth: auth, request: request, timeout: TimeInterval(0))
let lookup = try await net.cdsiLookup(auth: auth, request: request)
let response = try await lookup.complete()
for entry in response.entries {
_ = entry.aci
@ -130,8 +130,6 @@ final class Svr3Tests: TestCaseBase {
private let username = randomBytes(16).hexString
private let storedSecret = randomBytes(32)
private let defaultTimeout = TimeInterval(10)
func getEnclaveSecret() throws -> String {
guard let enclaveSecret = ProcessInfo.processInfo.environment["ENCLAVE_SECRET"] else {
throw XCTSkip("requires ENCLAVE_SECRET")
@ -147,15 +145,13 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 10,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
let restoredSecret = try await net.svr3.restore(
password: "password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTAssertEqual(restoredSecret, self.storedSecret)
}
@ -168,16 +164,14 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 10,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
do {
_ = try await net.svr3.restore(
password: "invalid password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.svrRestoreFailed(_) {
@ -195,8 +189,7 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 10,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
// Invert a byte somewhere inside the share set
shareSet[42] ^= 0xFF
@ -205,8 +198,7 @@ final class Svr3Tests: TestCaseBase {
_ = try await net.svr3.restore(
password: "password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.svrRestoreFailed(_) {
@ -224,23 +216,20 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 1,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
// First restore should succeed, but use up all the available tries
_ = try await net.svr3.restore(
password: "password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
do {
_ = try await net.svr3.restore(
password: "password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.svrDataMissing(_) {
@ -258,16 +247,14 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 1,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
// First restore fails **and** decrements the tries left counter
do {
_ = try await net.svr3.restore(
password: "invalid password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.svrRestoreFailed(_) {
@ -280,8 +267,7 @@ final class Svr3Tests: TestCaseBase {
_ = try await net.svr3.restore(
password: "password",
shareSet: shareSet,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.svrDataMissing(_) {
@ -300,8 +286,7 @@ final class Svr3Tests: TestCaseBase {
self.storedSecret,
password: "password",
maxTries: 0,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.invalidArgument(_) {
@ -320,8 +305,7 @@ final class Svr3Tests: TestCaseBase {
randomBytes(42),
password: "password",
maxTries: 0,
auth: auth,
timeout: self.defaultTimeout
auth: auth
)
XCTFail("Should have thrown")
} catch SignalError.invalidArgument(_) {
@ -330,27 +314,6 @@ final class Svr3Tests: TestCaseBase {
XCTFail("Unexpected error: \(error)")
}
}
func testBackupTimeout() async throws {
let auth = try Auth(username: self.username, enclaveSecret: self.getEnclaveSecret())
let net = Net(env: .staging)
do {
_ = try await net.svr3.backup(
self.storedSecret,
password: "password",
maxTries: 1,
auth: auth,
timeout: TimeInterval(0.01)
)
XCTFail("Should have thrown")
} catch SignalError.timeoutError(let message) {
// Make sure the logged message will provide enough details
XCTAssertTrue(message.contains("Operation timed out"), "Unexpected message: '\(message)'")
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
#endif