diff --git a/app/build.gradle b/app/build.gradle index 0967d372a8..4af80edc37 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,6 +38,10 @@ wire { sourcePath { srcDir 'src/main/protowire' } + + protoPath { + srcDir "${project.rootDir}/libsignal/service/src/main/protowire" + } } ktlint { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt index f020f7c6f2..93e762c2c6 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt @@ -27,8 +27,8 @@ import org.thoughtcrime.securesms.testing.FakeClientHelpers import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.awaitFor import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage +import org.whispersystems.signalservice.internal.websocket.WebSocketMessage +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage import java.util.regex.Pattern import kotlin.random.Random import kotlin.time.Duration.Companion.minutes @@ -179,32 +179,19 @@ class MessageProcessingPerformanceTest { } private fun webSocketTombstone(): ByteString { - return WebSocketMessage - .newBuilder() - .setRequest( - WebSocketRequestMessage.newBuilder() - .setVerb("PUT") - .setPath("/api/v1/queue/empty") - ) - .build() - .toByteArray() - .toByteString() + return WebSocketMessage(request = WebSocketRequestMessage(verb = "PUT", path = "/api/v1/queue/empty")).encodeByteString() } private fun Envelope.toWebSocketPayload(): ByteString { - return WebSocketMessage - .newBuilder() - .setType(WebSocketMessage.Type.REQUEST) - .setRequest( - WebSocketRequestMessage.newBuilder() - .setVerb("PUT") - .setPath("/api/v1/message") - .setId(Random(System.currentTimeMillis()).nextLong()) - .addHeaders("X-Signal-Timestamp: ${this.timestamp}") - .setBody(this.toByteString()) + return WebSocketMessage( + type = WebSocketMessage.Type.REQUEST, + request = WebSocketRequestMessage( + verb = "PUT", + path = "/api/v1/message", + id = Random(System.currentTimeMillis()).nextLong(), + headers = listOf("X-Signal-Timestamp: ${this.timestamp}"), + body = this.toByteArray().toByteString() ) - .build() - .toByteArray() - .toByteString() + ).encodeByteString() } } diff --git a/core-util/build.gradle b/core-util/build.gradle index c53ac1aad0..58d06e30a3 100644 --- a/core-util/build.gradle +++ b/core-util/build.gradle @@ -2,6 +2,7 @@ plugins { id 'signal-library' id 'com.google.protobuf' id 'kotlin-kapt' + id 'com.squareup.wire' } android { @@ -33,3 +34,13 @@ dependencies { exclude group: 'com.google.protobuf', module: 'protobuf-java' } } + +wire { + kotlin { + javaInterop = true + } + + sourcePath { + srcDir 'src/main/protowire' + } +} \ No newline at end of file diff --git a/donations/lib/src/main/java/org/signal/donations/StripeApi.kt b/donations/lib/src/main/java/org/signal/donations/StripeApi.kt index b39ea6f5b0..74365a33a1 100644 --- a/donations/lib/src/main/java/org/signal/donations/StripeApi.kt +++ b/donations/lib/src/main/java/org/signal/donations/StripeApi.kt @@ -15,6 +15,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okio.ByteString +import okio.ByteString.Companion.encodeUtf8 import org.json.JSONObject import org.signal.core.util.logging.Log import org.signal.core.util.money.FiatMoney @@ -263,7 +264,7 @@ class StripeApi( private fun getRequestBuilder(endpoint: String): Request.Builder { return Request.Builder() .url("${configuration.baseUrl}/$endpoint") - .addHeader("Authorization", "Basic ${ByteString.encodeUtf8("${configuration.publishableKey}:").base64()}") + .addHeader("Authorization", "Basic ${"${configuration.publishableKey}:".encodeUtf8().base64()}") } private fun checkResponseForErrors(response: Response): Response { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java index 4be5ffc8d0..e5bf352953 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/KeyBackupService.java @@ -175,14 +175,13 @@ public class KeyBackupService { final KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName); final RestoreResponse status = KeyBackupCipher.getKeyRestoreResponse(response, remoteAttestation); - TokenResponse nextToken = status.hasToken() - ? new TokenResponse(token.getBackupId(), status.getToken().toByteArray(), status.getTries()) - : token; + TokenResponse nextToken = status.token != null ? new TokenResponse(token.getBackupId(), status.token.toByteArray(), status.tries) + : token; - Log.i(TAG, "Restore " + status.getStatus()); - switch (status.getStatus()) { + Log.i(TAG, "Restore " + status.status); + switch (status.status) { case OK: - KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, status.getData().toByteArray()); + KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, status.data_.toByteArray()); MasterKey masterKey = kbsData.getMasterKey(); return new SvrPinData(masterKey, nextToken); case PIN_MISMATCH: @@ -191,8 +190,8 @@ public class KeyBackupService { case TOKEN_MISMATCH: Log.i(TAG, "Restore TOKEN_MISMATCH"); // if the number of tries has not fallen, the pin is correct we're just using an out of date token - boolean canRetry = remainingTries == status.getTries(); - Log.i(TAG, String.format(Locale.US, "Token MISMATCH remainingTries: %d, status.getTries(): %d", remainingTries, status.getTries())); + boolean canRetry = remainingTries == status.tries; + Log.i(TAG, String.format(Locale.US, "Token MISMATCH remainingTries: %d, status.getTries(): %d", remainingTries, status.tries)); throw new TokenException(nextToken, canRetry); case MISSING: Log.i(TAG, "Restore OK! No data though"); @@ -259,11 +258,11 @@ public class KeyBackupService { KeyBackupRequest request = KeyBackupCipher.createKeyBackupRequest(kbsAccessKey, kbsData, token, remoteAttestation, serviceId, maxTries); KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName); BackupResponse backupResponse = KeyBackupCipher.getKeyBackupResponse(response, remoteAttestation); - BackupResponse.Status status = backupResponse.getStatus(); + BackupResponse.Status status = backupResponse.status; switch (status) { case OK: - return backupResponse.hasToken() ? new TokenResponse(token.getBackupId(), backupResponse.getToken().toByteArray(), maxTries) : token; + return backupResponse.token != null ? new TokenResponse(token.getBackupId(), backupResponse.token.toByteArray(), maxTries) : token; case ALREADY_EXISTS: throw new UnauthenticatedResponseException("Already exists"); case NOT_YET_VALID: diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java index 709c338131..44c444e5e2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java @@ -1,7 +1,5 @@ package org.whispersystems.signalservice.api; -import com.google.protobuf.InvalidProtocolBufferException; - import org.signal.libsignal.protocol.logging.Log; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.messages.EnvelopeResponse; @@ -10,8 +8,8 @@ import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage; +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage; +import org.whispersystems.signalservice.internal.websocket.WebSocketResponseMessage; import org.whispersystems.signalservice.internal.websocket.WebsocketResponse; import org.whispersystems.util.Base64; @@ -202,9 +200,11 @@ public final class SignalWebSocket { public Single request(WebSocketRequestMessage requestMessage, Optional unidentifiedAccess) { if (unidentifiedAccess.isPresent()) { - WebSocketRequestMessage message = WebSocketRequestMessage.newBuilder(requestMessage) - .addHeaders("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey())) - .build(); + List headers = new ArrayList<>(requestMessage.headers); + headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey())); + WebSocketRequestMessage message = requestMessage.newBuilder() + .headers(headers) + .build(); try { return getUnidentifiedWebSocket().sendRequest(message) .flatMap(r -> { @@ -299,7 +299,7 @@ public final class SignalWebSocket { } private static EnvelopeResponse requestToEnvelopeResponse(WebSocketRequestMessage request) - throws InvalidProtocolBufferException + throws IOException { Optional timestampHeader = findHeader(request); long timestamp = 0; @@ -312,41 +312,41 @@ public final class SignalWebSocket { } } - SignalServiceProtos.Envelope envelope = SignalServiceProtos.Envelope.parseFrom(request.getBody().toByteArray()); + SignalServiceProtos.Envelope envelope = SignalServiceProtos.Envelope.parseFrom(request.body.toByteArray()); return new EnvelopeResponse(envelope, timestamp, request); } private static boolean isSignalServiceEnvelope(WebSocketRequestMessage message) { - return "PUT".equals(message.getVerb()) && "/api/v1/message".equals(message.getPath()); + return "PUT".equals(message.verb) && "/api/v1/message".equals(message.path); } private static boolean isSocketEmptyRequest(WebSocketRequestMessage message) { - return "PUT".equals(message.getVerb()) && "/api/v1/queue/empty".equals(message.getPath()); + return "PUT".equals(message.verb) && "/api/v1/queue/empty".equals(message.path); } private static WebSocketResponseMessage createWebSocketResponse(WebSocketRequestMessage request) { if (isSignalServiceEnvelope(request)) { - return WebSocketResponseMessage.newBuilder() - .setId(request.getId()) - .setStatus(200) - .setMessage("OK") - .build(); + return new WebSocketResponseMessage.Builder() + .id(request.id) + .status(200) + .message("OK") + .build(); } else { - return WebSocketResponseMessage.newBuilder() - .setId(request.getId()) - .setStatus(400) - .setMessage("Unknown") - .build(); + return new WebSocketResponseMessage.Builder() + .id(request.id) + .status(400) + .message("Unknown") + .build(); } } private static Optional findHeader(WebSocketRequestMessage message) { - if (message.getHeadersCount() == 0) { + if (message.headers.isEmpty()) { return Optional.empty(); } - for (String header : message.getHeadersList()) { + for (String header : message.headers) { if (header.startsWith(SERVER_DELIVERED_TIMESTAMP_HEADER)) { String[] split = header.split(":"); if (split.length == 2 && split[0].trim().toLowerCase().equals(SERVER_DELIVERED_TIMESTAMP_HEADER.toLowerCase())) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeResponse.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeResponse.kt index f086a3d477..b44848a928 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeResponse.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeResponse.kt @@ -1,7 +1,7 @@ package org.whispersystems.signalservice.api.messages import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage /** * Represents an envelope off the wire, paired with the metadata needed to process it. diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/AttachmentService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/AttachmentService.java index 292780ea1e..ee8ce2095d 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/AttachmentService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/AttachmentService.java @@ -6,7 +6,7 @@ import org.whispersystems.signalservice.internal.ServiceResponseProcessor; import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes; import org.whispersystems.signalservice.internal.push.AttachmentV3UploadAttributes; import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper; -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage; import java.security.SecureRandom; @@ -25,11 +25,11 @@ public final class AttachmentService { } public Single> getAttachmentV2UploadAttributes() { - WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() - .setId(new SecureRandom().nextLong()) - .setVerb("GET") - .setPath("/v2/attachments/form/upload") - .build(); + WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder() + .id(new SecureRandom().nextLong()) + .verb("GET") + .path("/v2/attachments/form/upload") + .build(); return signalWebSocket.request(requestMessage) .map(DefaultResponseMapper.getDefault(AttachmentV2UploadAttributes.class)::map) @@ -37,11 +37,11 @@ public final class AttachmentService { } public Single> getAttachmentV3UploadAttributes() { - WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() - .setId(new SecureRandom().nextLong()) - .setVerb("GET") - .setPath("/v3/attachments/form/upload") - .build(); + WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder() + .id(new SecureRandom().nextLong()) + .verb("GET") + .path("/v3/attachments/form/upload") + .build(); return signalWebSocket.request(requestMessage) .map(DefaultResponseMapper.getDefault(AttachmentV3UploadAttributes.class)::map) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java index 37eb7f6403..1fbd16a2da 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java @@ -128,7 +128,7 @@ final class CdsiSocket { Log.d(TAG, "[onMessage] Handshake read success."); Log.d(TAG, "[onMessage] Sending data..."); - byte[] ciphertextBytes = client.establishedSend(clientRequest.toByteArray()); + byte[] ciphertextBytes = client.establishedSend(clientRequest.encode()); webSocket.send(okio.ByteString.of(ciphertextBytes)); Log.d(TAG, "[onMessage] Data sent."); @@ -136,24 +136,24 @@ final class CdsiSocket { break; case WAITING_FOR_TOKEN: - ClientResponse tokenResponse = ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray())); + ClientResponse tokenResponse = ClientResponse.ADAPTER.decode(client.establishedRecv(bytes.toByteArray())); - if (tokenResponse.getToken().isEmpty()) { + if (tokenResponse.token.size() == 0) { throw new IOException("No token! Cannot continue!"); } - tokenSaver.accept(tokenResponse.getToken().toByteArray()); + tokenSaver.accept(tokenResponse.token.toByteArray()); Log.d(TAG, "[onMessage] Sending token ack..."); - webSocket.send(okio.ByteString.of(client.establishedSend(ClientRequest.newBuilder() - .setTokenAck(true) - .build() - .toByteArray()))); + webSocket.send(okio.ByteString.of(client.establishedSend(new ClientRequest.Builder() + .tokenAck(true) + .build() + .encode()))); stage.set(Stage.WAITING_FOR_RESPONSE); break; case WAITING_FOR_RESPONSE: - emitter.onNext(ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray()))); + emitter.onNext(ClientResponse.ADAPTER.decode(client.establishedRecv(bytes.toByteArray()))); break; case CLOSED: 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/services/CdsiV2Service.java index 1e9f22d10d..e69dc925d4 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java @@ -1,20 +1,19 @@ package org.whispersystems.signalservice.api.services; -import com.google.protobuf.ByteString; - import org.signal.cdsi.proto.ClientRequest; import org.signal.cdsi.proto.ClientResponse; 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.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; -import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collection; @@ -29,6 +28,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import io.reactivex.rxjava3.core.Single; +import okio.ByteString; /** * Handles network interactions with CDSI, the SGX-backed version of the CDSv2 API. @@ -75,7 +75,7 @@ public final class CdsiV2Service { private static Response parseEntries(ClientResponse clientResponse) { Map results = new HashMap<>(); - ByteBuffer parser = clientResponse.getE164PniAciTriples().asReadOnlyByteBuffer(); + ByteBuffer parser = clientResponse.e164PniAciTriples.asByteBuffer(); while (parser.remaining() >= RESPONSE_ITEM_SIZE) { String e164 = "+" + parser.getLong(); @@ -89,7 +89,7 @@ public final class CdsiV2Service { } } - return new Response(results, clientResponse.getDebugPermitsUsed()); + return new Response(results, clientResponse.debugPermitsUsed); } private static ClientRequest buildClientRequest(Request request) { @@ -97,22 +97,22 @@ public final class CdsiV2Service { List newE164s = parseAndSortE164Strings(request.newE164s); List removedE164s = parseAndSortE164Strings(request.removedE164s); - ClientRequest.Builder builder = ClientRequest.newBuilder() - .setPrevE164S(toByteString(previousE164s)) - .setNewE164S(toByteString(newE164s)) - .setDiscardE164S(toByteString(removedE164s)) - .setAciUakPairs(toByteString(request.serviceIds)) - .setReturnAcisWithoutUaks(request.requireAcis); + ClientRequest.Builder builder = new ClientRequest.Builder() + .prevE164s(toByteString(previousE164s)) + .newE164s(toByteString(newE164s)) + .discardE164s(toByteString(removedE164s)) + .aciUakPairs(toByteString(request.serviceIds)) + .returnAcisWithoutUaks(request.requireAcis); if (request.token != null) { - builder.setToken(ByteString.copyFrom(request.token)); + builder.token(ByteString.of(request.token)); } return builder.build(); } private static ByteString toByteString(List numbers) { - ByteString.Output os = ByteString.newOutput(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); for (long number : numbers) { try { @@ -122,11 +122,11 @@ public final class CdsiV2Service { } } - return os.toByteString(); + return ByteString.of(os.toByteArray()); } private static ByteString toByteString(Map serviceIds) { - ByteString.Output os = ByteString.newOutput(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); for (Map.Entry entry : serviceIds.entrySet()) { try { @@ -137,7 +137,7 @@ public final class CdsiV2Service { } } - return os.toByteString(); + return ByteString.of(os.toByteArray()); } private static List parseAndSortE164Strings(Collection e164s) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java index 9d4e63a145..0abb0e4840 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java @@ -1,7 +1,5 @@ package org.whispersystems.signalservice.api.services; -import com.google.protobuf.ByteString; - import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; @@ -20,7 +18,7 @@ import org.whispersystems.signalservice.internal.util.JsonUtil; import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper; import org.whispersystems.signalservice.internal.websocket.ResponseMapper; -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage; import org.whispersystems.util.Base64; import java.security.SecureRandom; @@ -30,6 +28,7 @@ import java.util.Locale; import java.util.Optional; import io.reactivex.rxjava3.core.Single; +import okio.ByteString; /** * Provide WebSocket based interface to message sending endpoints. @@ -48,13 +47,13 @@ public class MessagingService { add("content-type:application/json"); }}; - WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() - .setId(new SecureRandom().nextLong()) - .setVerb("PUT") - .setPath(String.format("/v1/messages/%s?story=%s", list.getDestination(), story ? "true" : "false")) - .addAllHeaders(headers) - .setBody(ByteString.copyFrom(JsonUtil.toJson(list).getBytes())) - .build(); + WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder() + .id(new SecureRandom().nextLong()) + .verb("PUT") + .path(String.format("/v1/messages/%s?story=%s", list.getDestination(), story ? "true" : "false")) + .headers(headers) + .body(ByteString.of(JsonUtil.toJson(list).getBytes())) + .build(); ResponseMapper responseMapper = DefaultResponseMapper.extend(SendMessageResponse.class) .withResponseMapper((status, body, getHeader, unidentified) -> { @@ -80,13 +79,13 @@ public class MessagingService { String path = String.format(Locale.US, "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s&story=%s", timestamp, online, urgent, story); - WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() - .setId(new SecureRandom().nextLong()) - .setVerb("PUT") - .setPath(path) - .addAllHeaders(headers) - .setBody(ByteString.copyFrom(body)) - .build(); + WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder() + .id(new SecureRandom().nextLong()) + .verb("PUT") + .path(path) + .headers(headers) + .body(ByteString.of(body)) + .build(); return signalWebSocket.request(requestMessage) .map(DefaultResponseMapper.extend(SendGroupMessageResponse.class) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index 5b30c5e04a..1b3ddad5ba 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -29,7 +29,7 @@ import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.signalservice.internal.util.JsonUtil; import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper; import org.whispersystems.signalservice.internal.websocket.ResponseMapper; -import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; +import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage; import java.security.SecureRandom; import java.util.Collections; @@ -78,9 +78,9 @@ public final class ProfileService { SecureRandom random = new SecureRandom(); ProfileKeyCredentialRequestContext requestContext = null; - WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder() - .setId(random.nextLong()) - .setVerb("GET"); + WebSocketRequestMessage.Builder builder = new WebSocketRequestMessage.Builder() + .id(random.nextLong()) + .verb("GET"); if (profileKey.isPresent()) { if (!(serviceId instanceof ACI)) { @@ -98,15 +98,15 @@ public final class ProfileService { ProfileKeyCredentialRequest request = requestContext.getRequest(); String credentialRequest = Hex.toStringCondensed(request.serialize()); - builder.setPath(String.format("/v1/profile/%s/%s/%s?credentialType=expiringProfileKey", serviceId, version, credentialRequest)); + builder.path(String.format("/v1/profile/%s/%s/%s?credentialType=expiringProfileKey", serviceId, version, credentialRequest)); } else { - builder.setPath(String.format("/v1/profile/%s/%s", serviceId, version)); + builder.path(String.format("/v1/profile/%s/%s", serviceId, version)); } } else { - builder.setPath(String.format("/v1/profile/%s", address.getIdentifier())); + builder.path(String.format("/v1/profile/%s", address.getIdentifier())); } - builder.addHeaders(AcceptLanguagesUtil.getAcceptLanguageHeader(locale)); + builder.headers(Collections.singletonList(AcceptLanguagesUtil.getAcceptLanguageHeader(locale))); WebSocketRequestMessage requestMessage = builder.build(); @@ -128,12 +128,12 @@ public final class ProfileService { IdentityCheckRequest request = new IdentityCheckRequest(serviceIdKeyPairs); - WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder() - .setId(new SecureRandom().nextLong()) - .setVerb("POST") - .setPath("/v1/profile/identity_check/batch") - .addAllHeaders(Collections.singleton("content-type:application/json")) - .setBody(JsonUtil.toJsonByteString(request)); + WebSocketRequestMessage.Builder builder = new WebSocketRequestMessage.Builder() + .id(new SecureRandom().nextLong()) + .verb("POST") + .path("/v1/profile/identity_check/batch") + .headers(Collections.singletonList("content-type:application/json")) + .body(JsonUtil.toJsonByteString(request)); ResponseMapper responseMapper = DefaultResponseMapper.getDefault(IdentityCheckResponse.class); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/contacts/crypto/KeyBackupCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/contacts/crypto/KeyBackupCipher.java index 93f326a4b7..0a5f2c81c2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/contacts/crypto/KeyBackupCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/contacts/crypto/KeyBackupCipher.java @@ -1,8 +1,5 @@ package org.whispersystems.signalservice.internal.contacts.crypto; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; - import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest; import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse; @@ -16,8 +13,11 @@ import org.whispersystems.signalservice.internal.keybackup.protos.Response; import org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest; import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse; +import java.io.IOException; import java.util.concurrent.TimeUnit; +import okio.ByteString; + public final class KeyBackupCipher { private KeyBackupCipher() { @@ -34,17 +34,17 @@ public final class KeyBackupCipher { { long now = System.currentTimeMillis(); - BackupRequest backupRequest = BackupRequest.newBuilder() - .setServiceId(ByteString.copyFrom(serviceId)) - .setBackupId(ByteString.copyFrom(token.getBackupId())) - .setToken(ByteString.copyFrom(token.getToken())) - .setValidFrom(getValidFromSeconds(now)) - .setData(ByteString.copyFrom(kbsData)) - .setPin(ByteString.copyFrom(kbsAccessKey)) - .setTries(tries) - .build(); + BackupRequest backupRequest = new BackupRequest.Builder() + .serviceId(ByteString.of(serviceId)) + .backupId(ByteString.of(token.getBackupId())) + .token(ByteString.of(token.getToken())) + .validFrom(getValidFromSeconds(now)) + .data_(ByteString.of(kbsData)) + .pin(ByteString.of(kbsAccessKey)) + .tries(tries) + .build(); - Request requestData = Request.newBuilder().setBackup(backupRequest).build(); + Request requestData = new Request.Builder().backup(backupRequest).build(); return createKeyBackupRequest(requestData, remoteAttestation, "backup"); } @@ -56,15 +56,15 @@ public final class KeyBackupCipher { { long now = System.currentTimeMillis(); - RestoreRequest restoreRequest = RestoreRequest.newBuilder() - .setServiceId(ByteString.copyFrom(serviceId)) - .setBackupId(ByteString.copyFrom(token.getBackupId())) - .setToken(ByteString.copyFrom(token.getToken())) - .setValidFrom(getValidFromSeconds(now)) - .setPin(ByteString.copyFrom(kbsAccessKey)) - .build(); + RestoreRequest restoreRequest = new RestoreRequest.Builder() + .serviceId(ByteString.of(serviceId)) + .backupId(ByteString.of(token.getBackupId())) + .token(ByteString.of(token.getToken())) + .validFrom(getValidFromSeconds(now)) + .pin(ByteString.of(kbsAccessKey)) + .build(); - Request request = Request.newBuilder().setRestore(restoreRequest).build(); + Request request = new Request.Builder().restore(restoreRequest).build(); return createKeyBackupRequest(request, remoteAttestation, "restore"); } @@ -73,47 +73,47 @@ public final class KeyBackupCipher { RemoteAttestation remoteAttestation, byte[] serviceId) { - DeleteRequest deleteRequest = DeleteRequest.newBuilder() - .setServiceId(ByteString.copyFrom(serviceId)) - .setBackupId(ByteString.copyFrom(token.getBackupId())) - .build(); + DeleteRequest deleteRequest = new DeleteRequest.Builder() + .serviceId(ByteString.of(serviceId)) + .backupId(ByteString.of(token.getBackupId())) + .build(); - Request request = Request.newBuilder().setDelete(deleteRequest).build(); + Request request = new Request.Builder().delete(deleteRequest).build(); return createKeyBackupRequest(request, remoteAttestation, "delete"); } public static BackupResponse getKeyBackupResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation) - throws InvalidCiphertextException, InvalidProtocolBufferException + throws InvalidCiphertextException, IOException { byte[] data = decryptData(response, remoteAttestation); - Response backupResponse = Response.parseFrom(data); + Response backupResponse = Response.ADAPTER.decode(data); - return backupResponse.getBackup(); + return backupResponse.backup; } public static RestoreResponse getKeyRestoreResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation) - throws InvalidCiphertextException, InvalidProtocolBufferException + throws InvalidCiphertextException, IOException { byte[] data = decryptData(response, remoteAttestation); - return Response.parseFrom(data).getRestore(); + return Response.ADAPTER.decode(data).restore; } public static DeleteResponse getKeyDeleteResponseStatus(KeyBackupResponse response, RemoteAttestation remoteAttestation) - throws InvalidCiphertextException, InvalidProtocolBufferException + throws InvalidCiphertextException, IOException { byte[] data = decryptData(response, remoteAttestation); - return DeleteResponse.parseFrom(data); + return DeleteResponse.ADAPTER.decode(data); } private static KeyBackupRequest createKeyBackupRequest(Request requestData, RemoteAttestation remoteAttestation, String type) { byte[] clientKey = remoteAttestation.getKeys().getClientKey(); byte[] aad = remoteAttestation.getRequestId(); - AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData.toByteArray()); + AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData.encode()); return new KeyBackupRequest(aesEncryptedResult.aad, aesEncryptedResult.iv, aesEncryptedResult.data, aesEncryptedResult.mac, type); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java index 9478d063aa..05fd09ed60 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/util/JsonUtil.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.module.kotlin.KotlinModule; -import com.google.protobuf.ByteString; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; @@ -35,6 +34,8 @@ import java.util.UUID; import javax.annotation.Nonnull; +import okio.ByteString; + @SuppressWarnings("unused") public class JsonUtil { @@ -57,7 +58,7 @@ public class JsonUtil { } public static @Nonnull ByteString toJsonByteString(@Nonnull Object object) { - return ByteString.copyFrom(toJson(object).getBytes()); + return ByteString.of(toJson(object).getBytes()); } public static T fromJson(String json, Class clazz) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java index a1d876e4d0..fdecbfbcb1 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java @@ -1,7 +1,5 @@ package org.whispersystems.signalservice.internal.websocket; -import com.google.protobuf.InvalidProtocolBufferException; - import org.signal.libsignal.protocol.logging.Log; import org.signal.libsignal.protocol.util.Pair; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -52,10 +50,6 @@ import okhttp3.WebSocket; import okhttp3.WebSocketListener; import okio.ByteString; -import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage; -import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; -import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage; - public class WebSocketConnection extends WebSocketListener { private static final String TAG = WebSocketConnection.class.getSimpleName(); @@ -228,16 +222,16 @@ public class WebSocketConnection extends WebSocketListener { throw new IOException("No connection!"); } - WebSocketMessage message = WebSocketMessage.newBuilder() - .setType(WebSocketMessage.Type.REQUEST) - .setRequest(request) - .build(); + WebSocketMessage message = new WebSocketMessage.Builder() + .type(WebSocketMessage.Type.REQUEST) + .request(request) + .build(); SingleSubject single = SingleSubject.create(); - outgoingRequests.put(request.getId(), new OutgoingRequest(single)); + outgoingRequests.put(request.id, new OutgoingRequest(single)); - if (!client.send(ByteString.of(message.toByteArray()))) { + if (!client.send(ByteString.of(message.encode()))) { throw new IOException("Write failed!"); } @@ -251,12 +245,12 @@ public class WebSocketConnection extends WebSocketListener { throw new IOException("Connection closed!"); } - WebSocketMessage message = WebSocketMessage.newBuilder() - .setType(WebSocketMessage.Type.RESPONSE) - .setResponse(response) - .build(); + WebSocketMessage message = new WebSocketMessage.Builder() + .type(WebSocketMessage.Type.RESPONSE) + .response(response) + .build(); - if (!client.send(ByteString.of(message.toByteArray()))) { + if (!client.send(ByteString.of(message.encode()))) { throw new IOException("Write failed!"); } } @@ -265,15 +259,15 @@ public class WebSocketConnection extends WebSocketListener { if (client != null) { log( "Sending keep alive..."); long id = System.currentTimeMillis(); - byte[] message = WebSocketMessage.newBuilder() - .setType(WebSocketMessage.Type.REQUEST) - .setRequest(WebSocketRequestMessage.newBuilder() - .setId(id) - .setPath("/v1/keepalive") - .setVerb("GET") - .build()) - .build() - .toByteArray(); + byte[] message = new WebSocketMessage.Builder() + .type(WebSocketMessage.Type.REQUEST) + .request(new WebSocketRequestMessage.Builder() + .id(id) + .path("/v1/keepalive") + .verb("GET") + .build()) + .build() + .encode(); keepAlives.add(id); if (!client.send(ByteString.of(message))) { throw new IOException("Write failed!"); @@ -292,27 +286,27 @@ public class WebSocketConnection extends WebSocketListener { @Override public synchronized void onMessage(WebSocket webSocket, ByteString payload) { try { - WebSocketMessage message = WebSocketMessage.parseFrom(payload.toByteArray()); + WebSocketMessage message = WebSocketMessage.ADAPTER.decode(payload.toByteArray()); - if (message.getType().getNumber() == WebSocketMessage.Type.REQUEST_VALUE) { - incomingRequests.add(message.getRequest()); - } else if (message.getType().getNumber() == WebSocketMessage.Type.RESPONSE_VALUE) { - OutgoingRequest listener = outgoingRequests.remove(message.getResponse().getId()); + if (message.type == WebSocketMessage.Type.REQUEST) { + incomingRequests.add(message.request); + } else if (message.type == WebSocketMessage.Type.RESPONSE) { + OutgoingRequest listener = outgoingRequests.remove(message.response.id); if (listener != null) { - listener.onSuccess(new WebsocketResponse(message.getResponse().getStatus(), - new String(message.getResponse().getBody().toByteArray()), - message.getResponse().getHeadersList(), + listener.onSuccess(new WebsocketResponse(message.response.status, + new String(message.response.body.toByteArray()), + message.response.headers, !credentialsProvider.isPresent())); - if (message.getResponse().getStatus() >= 400) { - healthMonitor.onMessageError(message.getResponse().getStatus(), credentialsProvider.isPresent()); + if (message.response.status >= 400) { + healthMonitor.onMessageError(message.response.status, credentialsProvider.isPresent()); } - } else if (keepAlives.remove(message.getResponse().getId())) { - healthMonitor.onKeepAliveResponse(message.getResponse().getId(), credentialsProvider.isPresent()); + } else if (keepAlives.remove(message.response.id)) { + healthMonitor.onKeepAliveResponse(message.response.id, credentialsProvider.isPresent()); } } notifyAll(); - } catch (InvalidProtocolBufferException e) { + } catch (IOException e) { warn(e); } } diff --git a/libsignal/service/src/main/proto/CDSI.proto b/libsignal/service/src/main/protowire/CDSI.proto similarity index 89% rename from libsignal/service/src/main/proto/CDSI.proto rename to libsignal/service/src/main/protowire/CDSI.proto index bd1b2ce4f5..b14ee9ad87 100644 --- a/libsignal/service/src/main/proto/CDSI.proto +++ b/libsignal/service/src/main/protowire/CDSI.proto @@ -8,12 +8,12 @@ package org.signal.cdsi; message ClientRequest { // Each ACI/UAK pair is a 32-byte buffer, containing the 16-byte ACI followed // by its 16-byte UAK. - bytes aci_uak_pairs = 1; + bytes aciUakPairs = 1; // Each E164 is an 8-byte big-endian number, as 8 bytes. - bytes prev_e164s = 2; - bytes new_e164s = 3; - bytes discard_e164s = 4; + bytes prevE164s = 2; + bytes newE164s = 3; + bytes discardE164s = 4; // If true, the client has more pairs or e164s to send. If false or unset, // this is the client's last request, and processing should commence. @@ -27,11 +27,11 @@ message ClientRequest { // After receiving a new token from the server, send back a message just // containing a token_ack. - bool token_ack = 7; + bool tokenAck = 7; // Request that, if the server allows, both ACI and PNI be returned even // if the aci_uak_pairs don't match. - bool return_acis_without_uaks = 8; + bool returnAcisWithoutUaks = 8; } message ClientResponse { @@ -47,14 +47,14 @@ message ClientResponse { // where the additional 2 bytes are the id/type/length additions of the // protobuf marshaling added to each byte array. This avoids any data // leakage based on the size of the encrypted output. - bytes e164_pni_aci_triples = 1; + bytes e164PniAciTriples = 1; // If the user has run out of quota for lookups, they will receive // a response with just the following field set, followed by a websocket // closure of type 4008 (RESOURCE_EXHAUSTED). Should they retry exactly // the same request after the provided number of seconds has passed, // we expect it should work. - int32 retry_after_secs = 2; + int32 retryAfterSecs = 2; // A token which allows subsequent calls' rate limiting to discount the // e164s sent up in this request, only counting those in the next @@ -64,28 +64,28 @@ message ClientResponse { // On a successful response to a token_ack request, the number of permits // that were deducted from the user's rate-limit in order to process the // request - int32 debug_permits_used = 4; + int32 debugPermitsUsed = 4; } message EnclaveLoad { // If set, before loading any tuples entirely clear the current map, // zero'ing out all current data. - bool clear_all = 1; + bool clearAll = 1; // Each tuple is an 8-byte e164, a 16-byte PNI, a 16-byte ACI, and a // 16-byte UAK. These should be loaded as a 48-byte value (PNI,ACI,UAK) // associated with an 8-byte key (e164). // ACI/PNI/UAK may all be zeros, in which case this is a delete of the e164. - bytes e164_aci_pni_uak_tuples = 2; + bytes e164AciPniUakTuples = 2; // If non-empty, overwrite the shared token secret with this value. - bytes shared_token_secret = 3; + bytes sharedTokenSecret = 3; } message ClientHandshakeStart { // Public key associated with this server's enclave. For use in test-only // contexts where attestation is not available - bytes test_only_pubkey = 1; + bytes testonlyPubkey = 1; // Remote-attestation evidence associated with the public key bytes evidence = 2; diff --git a/libsignal/service/src/main/proto/KeyBackupService.proto b/libsignal/service/src/main/protowire/KeyBackupService.proto similarity index 70% rename from libsignal/service/src/main/proto/KeyBackupService.proto rename to libsignal/service/src/main/protowire/KeyBackupService.proto index a339fda9fc..4f61da13d4 100644 --- a/libsignal/service/src/main/proto/KeyBackupService.proto +++ b/libsignal/service/src/main/protowire/KeyBackupService.proto @@ -23,13 +23,13 @@ message Response { } message BackupRequest { - optional bytes service_id = 1; - optional bytes backup_id = 2; - optional bytes token = 3; - optional uint64 valid_from = 4; - optional bytes data = 5; - optional bytes pin = 6; - optional uint32 tries = 7; + optional bytes serviceId = 1; + optional bytes backupId = 2; + optional bytes token = 3; + optional uint64 validFrom = 4; + optional bytes data = 5; + optional bytes pin = 6; + optional uint32 tries = 7; } message BackupResponse { @@ -44,11 +44,11 @@ message BackupResponse { } message RestoreRequest { - optional bytes service_id = 1; - optional bytes backup_id = 2; - optional bytes token = 3; - optional uint64 valid_from = 4; - optional bytes pin = 5; + optional bytes serviceId = 1; + optional bytes backupId = 2; + optional bytes token = 3; + optional uint64 validFrom = 4; + optional bytes pin = 5; } message RestoreResponse { @@ -67,8 +67,8 @@ message RestoreResponse { } message DeleteRequest { - optional bytes service_id = 1; - optional bytes backup_id = 2; + optional bytes serviceId = 1; + optional bytes backupId = 2; } message DeleteResponse { diff --git a/libsignal/service/src/main/proto/WebSocketResources.proto b/libsignal/service/src/main/protowire/WebSocketResources.proto similarity index 100% rename from libsignal/service/src/main/proto/WebSocketResources.proto rename to libsignal/service/src/main/protowire/WebSocketResources.proto