diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt index 011a42f964..e502eb3250 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt @@ -11,13 +11,15 @@ import org.thoughtcrime.securesms.database.RecipientTable.CdsV2Result import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.net.SignalNetwork import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.SignalE164Util +import org.whispersystems.signalservice.api.NetworkResult +import org.whispersystems.signalservice.api.cds.CdsiV2Service import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException -import org.whispersystems.signalservice.api.services.CdsiV2Service import java.io.IOException import java.util.Optional import kotlin.math.roundToInt @@ -89,25 +91,39 @@ object ContactDiscoveryRefreshV2 { @WorkerThread @Synchronized fun lookupE164(e164: String): ContactDiscovery.LookupResult? { - val response: CdsiV2Service.Response = try { - AppDependencies.signalServiceAccountManager.getRegisteredUsersWithCdsi( - emptySet(), - setOf(e164), - SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), - Optional.empty(), - 10_000, - AppDependencies.libsignalNetwork, - RemoteConfig.libsignalRouteBasedCDSILookup - ) { - Log.i(TAG, "Ignoring token for one-off lookup.") + val result = SignalNetwork.cdsApi.getRegisteredUsers( + previousE164s = emptySet(), + newE164s = setOf(e164), + serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), + token = Optional.empty(), + timeoutMs = 10_000, + libsignalNetwork = AppDependencies.libsignalNetwork, + useLibsignalRouteBasedCDSIConnectionLogic = RemoteConfig.libsignalRouteBasedCDSILookup + ) { + Log.i(TAG, "Ignoring token for one-off lookup.") + } + + val response = when (result) { + is NetworkResult.Success -> result.result + is NetworkResult.StatusCodeError -> { + when (val e = result.exception) { + is CdsiResourceExhaustedException -> { + Log.w(TAG, "CDS resource exhausted! Can try again in ${e.retryAfterSeconds} seconds.") + SignalStore.misc.cdsBlockedUtil = System.currentTimeMillis() + e.retryAfterSeconds.seconds.inWholeMilliseconds + throw e + } + + is CdsiInvalidTokenException -> { + Log.w(TAG, "We did not provide a token, but still got a token error! Unexpected, but ignoring.") + throw e + } + + else -> throw e + } } - } catch (e: CdsiResourceExhaustedException) { - Log.w(TAG, "CDS resource exhausted! Can try again in ${e.retryAfterSeconds} seconds.") - SignalStore.misc.cdsBlockedUtil = System.currentTimeMillis() + e.retryAfterSeconds.seconds.inWholeMilliseconds - throw e - } catch (e: CdsiInvalidTokenException) { - Log.w(TAG, "We did not provide a token, but still got a token error! Unexpected, but ignoring.") - throw e + + is NetworkResult.NetworkError -> throw result.exception + is NetworkResult.ApplicationError -> throw RuntimeException("Unexpected exception", result.throwable) } return response.results[e164]?.let { item -> @@ -154,36 +170,50 @@ object ContactDiscoveryRefreshV2 { stopwatch.split("preamble") - val response: CdsiV2Service.Response = try { - AppDependencies.signalServiceAccountManager.getRegisteredUsersWithCdsi( - previousE164s, - newE164s, - SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), - Optional.ofNullable(token), - timeoutMs, - AppDependencies.libsignalNetwork, - RemoteConfig.libsignalRouteBasedCDSILookup - ) { tokenToSave -> - stopwatch.split("network-pre-token") - if (!isPartialRefresh) { - SignalStore.misc.cdsToken = tokenToSave - SignalDatabase.cds.updateAfterFullCdsQuery(previousE164s + newE164s, allE164s + newE164s) - Log.d(TAG, "Token saved!") - } else { - SignalDatabase.cds.updateAfterPartialCdsQuery(newE164s) - Log.d(TAG, "Ignoring token.") - } - stopwatch.split("cds-db") + val result = SignalNetwork.cdsApi.getRegisteredUsers( + previousE164s = previousE164s, + newE164s = newE164s, + serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), + token = Optional.ofNullable(token), + timeoutMs = timeoutMs, + libsignalNetwork = AppDependencies.libsignalNetwork, + useLibsignalRouteBasedCDSIConnectionLogic = RemoteConfig.libsignalRouteBasedCDSILookup + ) { tokenToSave -> + stopwatch.split("network-pre-token") + if (!isPartialRefresh) { + SignalStore.misc.cdsToken = tokenToSave + SignalDatabase.cds.updateAfterFullCdsQuery(previousE164s + newE164s, allE164s + newE164s) + Log.d(TAG, "Token saved!") + } else { + SignalDatabase.cds.updateAfterPartialCdsQuery(newE164s) + Log.d(TAG, "Ignoring token.") } - } catch (e: CdsiResourceExhaustedException) { - Log.w(TAG, "CDS resource exhausted! Can try again in ${e.retryAfterSeconds} seconds.") - SignalStore.misc.cdsBlockedUtil = System.currentTimeMillis() + e.retryAfterSeconds.seconds.inWholeMilliseconds - throw e - } catch (e: CdsiInvalidTokenException) { - Log.w(TAG, "Our token was invalid! Only thing we can do now is clear our local state :(") - SignalStore.misc.cdsToken = null - SignalDatabase.cds.clearAll() - throw e + stopwatch.split("cds-db") + } + + val response: CdsiV2Service.Response = when (result) { + is NetworkResult.Success -> result.result + is NetworkResult.StatusCodeError -> { + when (val e = result.exception) { + is CdsiResourceExhaustedException -> { + Log.w(TAG, "CDS resource exhausted! Can try again in ${e.retryAfterSeconds} seconds.") + SignalStore.misc.cdsBlockedUtil = System.currentTimeMillis() + e.retryAfterSeconds.seconds.inWholeMilliseconds + throw e + } + + is CdsiInvalidTokenException -> { + Log.w(TAG, "Our token was invalid! Only thing we can do now is clear our local state :(") + SignalStore.misc.cdsToken = null + SignalDatabase.cds.clearAll() + throw e + } + + else -> throw e + } + } + + is NetworkResult.NetworkError -> throw result.exception + is NetworkResult.ApplicationError -> throw RuntimeException("Unexpected exception", result.throwable) } if (!isPartialRefresh && SignalStore.misc.isCdsBlocked) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppDependencies.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppDependencies.kt index cecc9bc490..60a42a59eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppDependencies.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/AppDependencies.kt @@ -46,6 +46,7 @@ import org.whispersystems.signalservice.api.account.AccountApi import org.whispersystems.signalservice.api.archive.ArchiveApi import org.whispersystems.signalservice.api.attachment.AttachmentApi import org.whispersystems.signalservice.api.calling.CallingApi +import org.whispersystems.signalservice.api.cds.CdsApi import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations import org.whispersystems.signalservice.api.keys.KeysApi import org.whispersystems.signalservice.api.link.LinkDeviceApi @@ -322,6 +323,9 @@ object AppDependencies { val paymentsApi: PaymentsApi get() = networkModule.paymentsApi + val cdsApi: CdsApi + get() = networkModule.cdsApi + @JvmStatic val okHttpClient: OkHttpClient get() = networkModule.okHttpClient @@ -396,5 +400,6 @@ object AppDependencies { fun provideUsernameApi(unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): UsernameApi fun provideCallingApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, pushServiceSocket: PushServiceSocket): CallingApi fun providePaymentsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): PaymentsApi + fun provideCdsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): CdsApi } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 18cef51232..e7c94b85ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -84,6 +84,7 @@ import org.whispersystems.signalservice.api.account.AccountApi; import org.whispersystems.signalservice.api.archive.ArchiveApi; import org.whispersystems.signalservice.api.attachment.AttachmentApi; import org.whispersystems.signalservice.api.calling.CallingApi; +import org.whispersystems.signalservice.api.cds.CdsApi; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.keys.KeysApi; @@ -510,6 +511,11 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider { return new PaymentsApi(authWebSocket); } + @Override + public @NonNull CdsApi provideCdsApi(@NonNull SignalWebSocket.AuthenticatedWebSocket authWebSocket) { + return new CdsApi(authWebSocket); + } + @VisibleForTesting static class DynamicCredentialsProvider implements CredentialsProvider { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/NetworkDependenciesModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/NetworkDependenciesModule.kt index 3fdcf4de15..59550bc42f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/NetworkDependenciesModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/NetworkDependenciesModule.kt @@ -30,6 +30,7 @@ import org.whispersystems.signalservice.api.account.AccountApi import org.whispersystems.signalservice.api.archive.ArchiveApi import org.whispersystems.signalservice.api.attachment.AttachmentApi import org.whispersystems.signalservice.api.calling.CallingApi +import org.whispersystems.signalservice.api.cds.CdsApi import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations import org.whispersystems.signalservice.api.keys.KeysApi import org.whispersystems.signalservice.api.link.LinkDeviceApi @@ -172,6 +173,10 @@ class NetworkDependenciesModule( provider.providePaymentsApi(authWebSocket) } + val cdsApi: CdsApi by lazy { + provider.provideCdsApi(authWebSocket) + } + val okHttpClient: OkHttpClient by lazy { OkHttpClient.Builder() .addInterceptor(StandardUserAgentInterceptor()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/net/SignalNetwork.kt b/app/src/main/java/org/thoughtcrime/securesms/net/SignalNetwork.kt index acf3c4302c..e22d29ec87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/net/SignalNetwork.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/net/SignalNetwork.kt @@ -10,6 +10,7 @@ import org.whispersystems.signalservice.api.account.AccountApi import org.whispersystems.signalservice.api.archive.ArchiveApi import org.whispersystems.signalservice.api.attachment.AttachmentApi import org.whispersystems.signalservice.api.calling.CallingApi +import org.whispersystems.signalservice.api.cds.CdsApi import org.whispersystems.signalservice.api.keys.KeysApi import org.whispersystems.signalservice.api.link.LinkDeviceApi import org.whispersystems.signalservice.api.payments.PaymentsApi @@ -36,6 +37,9 @@ object SignalNetwork { val calling: CallingApi get() = AppDependencies.callingApi + val cdsApi: CdsApi + get() = AppDependencies.cdsApi + val keys: KeysApi get() = AppDependencies.keysApi diff --git a/app/src/test/java/org/thoughtcrime/securesms/dependencies/MockApplicationDependencyProvider.kt b/app/src/test/java/org/thoughtcrime/securesms/dependencies/MockApplicationDependencyProvider.kt index 75f920a4fc..276bc27324 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/dependencies/MockApplicationDependencyProvider.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/dependencies/MockApplicationDependencyProvider.kt @@ -40,6 +40,7 @@ import org.whispersystems.signalservice.api.account.AccountApi import org.whispersystems.signalservice.api.archive.ArchiveApi import org.whispersystems.signalservice.api.attachment.AttachmentApi import org.whispersystems.signalservice.api.calling.CallingApi +import org.whispersystems.signalservice.api.cds.CdsApi import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations import org.whispersystems.signalservice.api.keys.KeysApi import org.whispersystems.signalservice.api.link.LinkDeviceApi @@ -260,4 +261,8 @@ class MockApplicationDependencyProvider : AppDependencies.Provider { override fun providePaymentsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): PaymentsApi { return mockk(relaxed = true) } + + override fun provideCdsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): CdsApi { + return mockk(relaxed = true) + } } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 99fd6dbf1e..ffcb432963 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -29,13 +29,10 @@ import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.registration.RegistrationApi; -import org.whispersystems.signalservice.api.services.CdsiV2Service; import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2; import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV3; -import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.push.AuthCredentials; -import org.whispersystems.signalservice.internal.push.CdsiAuthResponse; import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts; import org.whispersystems.signalservice.internal.push.PaymentAddress; import org.whispersystems.signalservice.internal.push.ProfileAvatarData; @@ -52,15 +49,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; - -import javax.annotation.Nonnull; - -import io.reactivex.rxjava3.core.Single; /** * The main interface for creating, registering, and @@ -165,60 +156,6 @@ public class SignalServiceAccountManager { return this.pushServiceSocket.getAvailablePreKeys(serviceIdType); } - @SuppressWarnings("SameParameterValue") - public CdsiV2Service.Response getRegisteredUsersWithCdsi(Set previousE164s, - Set newE164s, - Map serviceIds, - Optional token, - Long timeoutMs, - @Nonnull Network libsignalNetwork, - boolean useLibsignalRouteBasedCDSIConnectionLogic, - Consumer tokenSaver) - throws IOException - { - CdsiAuthResponse auth = pushServiceSocket.getCdsiAuth(); - CdsiV2Service service = new CdsiV2Service(libsignalNetwork, useLibsignalRouteBasedCDSIConnectionLogic); - CdsiV2Service.Request request = new CdsiV2Service.Request(previousE164s, newE164s, serviceIds, token); - Single> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request, tokenSaver); - - ServiceResponse serviceResponse; - try { - if (timeoutMs == null) { - serviceResponse = single - .blockingGet(); - } else { - serviceResponse = single - .timeout(timeoutMs, TimeUnit.MILLISECONDS) - .blockingGet(); - } - } catch (RuntimeException e) { - Throwable cause = e.getCause(); - if (cause instanceof InterruptedException) { - throw new IOException("Interrupted", cause); - } else if (cause instanceof TimeoutException) { - throw new IOException("Timed out"); - } else { - throw e; - } - } catch (Exception e) { - throw new RuntimeException("Unexpected exception when retrieving registered users!", e); - } - - if (serviceResponse.getResult().isPresent()) { - return serviceResponse.getResult().get(); - } else if (serviceResponse.getApplicationError().isPresent()) { - if (serviceResponse.getApplicationError().get() instanceof IOException) { - throw (IOException) serviceResponse.getApplicationError().get(); - } else { - throw new IOException(serviceResponse.getApplicationError().get()); - } - } else if (serviceResponse.getExecutionError().isPresent()) { - throw new IOException(serviceResponse.getExecutionError().get()); - } else { - throw new IOException("Missing result!"); - } - } - public RemoteConfigResult getRemoteConfig() throws IOException { RemoteConfigResponse response = this.pushServiceSocket.getRemoteConfig(); Map out = new HashMap<>(); diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsApi.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsApi.kt new file mode 100644 index 0000000000..579abc1986 --- /dev/null +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsApi.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.signalservice.api.cds + +import org.signal.libsignal.net.Network +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.whispersystems.signalservice.api.NetworkResult +import org.whispersystems.signalservice.api.NetworkResult.StatusCodeError +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException +import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException +import org.whispersystems.signalservice.api.websocket.SignalWebSocket +import org.whispersystems.signalservice.internal.get +import org.whispersystems.signalservice.internal.push.CdsiAuthResponse +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage +import java.io.IOException +import java.util.Optional +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.function.Consumer + +/** + * Contact Discovery Service API endpoint. + */ +class CdsApi(private val authWebSocket: SignalWebSocket.AuthenticatedWebSocket) { + + /** + * Get CDS authentication and then request registered users for the provided e164s. + * + * GET /v2/directory/auth + * - 200: Success + * - 401: Not authenticated + * + * And then CDS websocket communications, can return the following within [StatusCodeError] + * - [CdsiResourceExhaustedException]: Rate limited + * - [CdsiInvalidTokenException]: Token no longer valid + */ + fun getRegisteredUsers( + previousE164s: Set, + newE164s: Set, + serviceIds: Map, + token: Optional, + timeoutMs: Long?, + libsignalNetwork: Network, + useLibsignalRouteBasedCDSIConnectionLogic: Boolean, + tokenSaver: Consumer + ): NetworkResult { + val authRequest = WebSocketRequestMessage.get("/v2/directory/auth") + + return NetworkResult.fromWebSocketRequest(authWebSocket, authRequest, CdsiAuthResponse::class) + .then { auth -> + val service = CdsiV2Service(libsignalNetwork, useLibsignalRouteBasedCDSIConnectionLogic) + val request = CdsiV2Service.Request(previousE164s, newE164s, serviceIds, token) + + val single = service.getRegisteredUsers(auth.username, auth.password, request, tokenSaver) + + return@then try { + if (timeoutMs == null) { + single + .blockingGet() + } else { + single + .timeout(timeoutMs, TimeUnit.MILLISECONDS) + .blockingGet() + } + } catch (e: RuntimeException) { + when (val cause = e.cause) { + is InterruptedException -> NetworkResult.NetworkError(IOException("Interrupted", cause)) + is TimeoutException -> NetworkResult.NetworkError(IOException("Timed out")) + else -> throw e + } + } catch (e: Exception) { + throw RuntimeException("Unexpected exception when retrieving registered users!", e) + } + } + } +} diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsiV2Service.java similarity index 60% rename from libsignal-service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java rename to libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsiV2Service.java index cf393c860b..9966999a33 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/cds/CdsiV2Service.java @@ -1,13 +1,15 @@ -package org.whispersystems.signalservice.api.services; +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.signalservice.api.cds; -import org.signal.cdsi.proto.ClientRequest; -import org.signal.cdsi.proto.ClientResponse; import org.signal.libsignal.net.CdsiLookupRequest; import org.signal.libsignal.net.CdsiLookupResponse; import org.signal.libsignal.net.Network; -import org.signal.libsignal.protocol.util.ByteUtil; import org.signal.libsignal.zkgroup.profiles.ProfileKey; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; +import org.whispersystems.signalservice.api.NetworkResult; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; @@ -15,21 +17,13 @@ import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentE import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException; import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.signalservice.internal.ServiceResponse; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.IllegalArgumentException; -import java.nio.ByteBuffer; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.function.Consumer; @@ -39,18 +33,12 @@ import javax.annotation.Nonnull; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; -import okio.ByteString; /** * Handles network interactions with CDSI, the SGX-backed version of the CDSv2 API. */ public final class CdsiV2Service { - private static final String TAG = CdsiV2Service.class.getSimpleName(); - - private static final UUID EMPTY_UUID = new UUID(0, 0); - private static final int RESPONSE_ITEM_SIZE = 8 + 16 + 16; // 1 uint64 + 2 UUIDs - private final CdsiRequestHandler cdsiRequestHandler; public CdsiV2Service(@Nonnull Network network, boolean useLibsignalRouteBasedCDSIConnectionLogic) { @@ -72,8 +60,9 @@ public final class CdsiV2Service { }; } - public Single> getRegisteredUsers(String username, String password, Request request, Consumer tokenSaver) { - return cdsiRequestHandler.handleRequest(username, password, request, tokenSaver) + public Single> getRegisteredUsers(String username, String password, Request request, Consumer tokenSaver) { + return cdsiRequestHandler + .handleRequest(username, password, request, tokenSaver) .collect(Collectors.toList()) .flatMap(pages -> { Map all = new HashMap<>(); @@ -84,85 +73,19 @@ public final class CdsiV2Service { quotaUsed += page.getQuotaUsedDebugOnly(); } - return Single.just(new Response(all, quotaUsed)); + return Single.>just(new NetworkResult.Success<>(new Response(all, quotaUsed))); }) - .map(result -> ServiceResponse.forResult(result, 200, null)) .onErrorReturn(error -> { if (error instanceof NonSuccessfulResponseCodeException) { - int status = ((NonSuccessfulResponseCodeException) error).code; - return ServiceResponse.forApplicationError(error, status, null); + return new NetworkResult.StatusCodeError<>((NonSuccessfulResponseCodeException) error); + } else if (error instanceof IOException) { + return new NetworkResult.NetworkError<>((IOException) error); } else { - return ServiceResponse.forUnknownError(error); + return new NetworkResult.ApplicationError<>(error); } }); } - private static Response parseEntries(ClientResponse clientResponse) { - Map results = new HashMap<>(); - ByteBuffer parser = clientResponse.e164PniAciTriples.asByteBuffer(); - - while (parser.remaining() >= RESPONSE_ITEM_SIZE) { - String e164 = "+" + parser.getLong(); - UUID pniUuid = new UUID(parser.getLong(), parser.getLong()); - UUID aciUuid = new UUID(parser.getLong(), parser.getLong()); - - if (!pniUuid.equals(EMPTY_UUID)) { - PNI pni = PNI.from(pniUuid); - ACI aci = aciUuid.equals(EMPTY_UUID) ? null : ACI.from(aciUuid); - results.put(e164, new ResponseItem(pni, Optional.ofNullable(aci))); - } - } - - return new Response(results, clientResponse.debugPermitsUsed); - } - - private static ClientRequest buildClientRequest(Request request) { - List previousE164s = parseAndSortE164Strings(request.previousE164s); - List newE164s = parseAndSortE164Strings(request.newE164s); - List removedE164s = parseAndSortE164Strings(request.removedE164s); - - ClientRequest.Builder builder = new ClientRequest.Builder() - .prevE164s(toByteString(previousE164s)) - .newE164s(toByteString(newE164s)) - .discardE164s(toByteString(removedE164s)) - .aciUakPairs(toByteString(request.serviceIds)); - - if (request.token != null) { - builder.token(ByteString.of(request.token)); - } - - return builder.build(); - } - - private static ByteString toByteString(List numbers) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - - for (long number : numbers) { - try { - os.write(ByteUtil.longToByteArray(number)); - } catch (IOException e) { - throw new AssertionError("Failed to write long to ByteString", e); - } - } - - return ByteString.of(os.toByteArray()); - } - - private static ByteString toByteString(Map serviceIds) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - - for (Map.Entry entry : serviceIds.entrySet()) { - try { - os.write(UuidUtil.toByteArray(entry.getKey().getRawUuid())); - os.write(UnidentifiedAccess.deriveAccessKeyFrom(entry.getValue())); - } catch (IOException e) { - throw new AssertionError("Failed to write long to ByteString", e); - } - } - - return ByteString.of(os.toByteArray()); - } - private static CdsiLookupRequest buildLibsignalRequest(Request request) { HashMap serviceIds = new HashMap<>(request.serviceIds.size()); request.serviceIds.forEach((key, value) -> serviceIds.put(key.getLibSignalServiceId(), value)); @@ -187,14 +110,6 @@ public final class CdsiV2Service { return lookupError; } - private static List parseAndSortE164Strings(Collection e164s) { - return e164s.stream() - .map(Long::parseLong) - .sorted() - .collect(Collectors.toList()); - - } - public static final class Request { final Set previousE164s; final Set newE164s; diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 90a97ded3e..0ed1a70e57 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -259,7 +259,6 @@ public class PushServiceSocket { private static final String REGISTRATION_PATH = "/v1/registration"; - private static final String CDSI_AUTH = "/v2/directory/auth"; private static final String SVR2_AUTH = "/v2/backup/auth"; private static final String SVR3_AUTH = "/v3/backup/auth"; @@ -427,11 +426,6 @@ public class PushServiceSocket { return JsonUtil.fromJson(response, VerifyAccountResponse.class); } - public CdsiAuthResponse getCdsiAuth() throws IOException { - String body = makeServiceRequest(CDSI_AUTH, "GET", null); - return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class); - } - public AuthCredentials getSvr2Authorization() throws IOException { String body = makeServiceRequest(SVR2_AUTH, "GET", null); AuthCredentials credentials = JsonUtil.fromJsonResponse(body, AuthCredentials.class);