From 4eb88a3e0294a9c63a20847628c6457848129eb9 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Fri, 25 Jul 2014 15:48:34 -0700 Subject: [PATCH] Server side support for delivery receipts. --- protobuf/OutgoingMessageSignal.proto | 9 + .../textsecuregcm/WhisperServerService.java | 3 + .../controllers/MessageController.java | 5 +- .../controllers/ReceiptController.java | 108 ++ .../controllers/WebsocketController.java | 40 +- .../entities/IncomingMessage.java | 2 +- .../entities/IncomingMessageList.java | 7 + .../textsecuregcm/entities/MessageProtos.java | 1373 +++++++++++------ .../entities/PendingMessage.java | 13 +- .../federation/FederatedClient.java | 20 + .../textsecuregcm/push/APNSender.java | 10 +- .../textsecuregcm/push/GCMSender.java | 4 +- .../textsecuregcm/push/PushSender.java | 10 +- .../textsecuregcm/push/WebsocketSender.java | 3 +- .../textsecuregcm/storage/Accounts.java | 8 +- .../storage/AccountsManager.java | 5 +- .../textsecuregcm/storage/PubSubManager.java | 3 +- .../textsecuregcm/storage/StoredMessages.java | 3 +- .../textsecuregcm/util/CORSHeaderFilter.java | 41 - .../textsecuregcm/util/SystemMapper.java | 20 + .../websocket/WebsocketControllerFactory.java | 7 +- .../controllers/ReceiptControllerTest.java | 91 ++ .../controllers/WebsocketControllerTest.java | 32 +- 23 files changed, 1259 insertions(+), 558 deletions(-) create mode 100644 src/main/java/org/whispersystems/textsecuregcm/controllers/ReceiptController.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/util/CORSHeaderFilter.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java create mode 100644 src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java diff --git a/protobuf/OutgoingMessageSignal.proto b/protobuf/OutgoingMessageSignal.proto index 510123e4b..aec9e8fb5 100644 --- a/protobuf/OutgoingMessageSignal.proto +++ b/protobuf/OutgoingMessageSignal.proto @@ -20,6 +20,15 @@ option java_package = "org.whispersystems.textsecuregcm.entities"; option java_outer_classname = "MessageProtos"; message OutgoingMessageSignal { + enum Type { + UNKNOWN = 0; + CIPHERTEXT = 1; + KEY_EXCHANGE = 2; + PREKEY_BUNDLE = 3; + PLAINTEXT = 4; + RECEIPT = 5; + } + optional uint32 type = 1; optional string source = 2; optional uint32 sourceDevice = 7; diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 21e8af6b1..c745e81a5 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -37,6 +37,7 @@ import org.whispersystems.textsecuregcm.controllers.FederationControllerV2; import org.whispersystems.textsecuregcm.controllers.KeysControllerV1; import org.whispersystems.textsecuregcm.controllers.KeysControllerV2; import org.whispersystems.textsecuregcm.controllers.MessageController; +import org.whispersystems.textsecuregcm.controllers.ReceiptController; import org.whispersystems.textsecuregcm.federation.FederatedClientManager; import org.whispersystems.textsecuregcm.federation.FederatedPeer; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -176,6 +177,7 @@ public class WhisperServerService extends Application destinationDevice = destination.getDevice(incomingMessage.getDestinationDeviceId()); if (destinationDevice.isPresent()) { - sendLocalMessage(source, destination, destinationDevice.get(), incomingMessage); + sendLocalMessage(source, destination, destinationDevice.get(), messages.getTimestamp(), incomingMessage); } } } @@ -150,6 +150,7 @@ public class MessageController { private void sendLocalMessage(Account source, Account destinationAccount, Device destinationDevice, + long timestamp, IncomingMessage incomingMessage) throws NoSuchUserException, IOException { @@ -159,7 +160,7 @@ public class MessageController { messageBuilder.setType(incomingMessage.getType()) .setSource(source.getNumber()) - .setTimestamp(System.currentTimeMillis()) + .setTimestamp(timestamp == 0 ? System.currentTimeMillis() : timestamp) .setSourceDevice((int)source.getAuthenticatedDevice().get().getId()); if (messageBody.isPresent()) { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/ReceiptController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/ReceiptController.java new file mode 100644 index 000000000..4a5ef2450 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/ReceiptController.java @@ -0,0 +1,108 @@ +package org.whispersystems.textsecuregcm.controllers; + +import com.codahale.metrics.annotation.Timed; +import com.google.common.base.Optional; +import org.whispersystems.textsecuregcm.federation.FederatedClientManager; +import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; +import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; +import org.whispersystems.textsecuregcm.push.PushSender; +import org.whispersystems.textsecuregcm.push.TransientPushFailureException; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; + +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.List; + +import io.dropwizard.auth.Auth; +import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal; + +@Path("/v1/receipt") +public class ReceiptController { + + private final AccountsManager accountManager; + private final PushSender pushSender; + private final FederatedClientManager federatedClientManager; + + public ReceiptController(AccountsManager accountManager, + FederatedClientManager federatedClientManager, + PushSender pushSender) + { + this.accountManager = accountManager; + this.federatedClientManager = federatedClientManager; + this.pushSender = pushSender; + } + + @Timed + @PUT + @Path("/{destination}/{messageId}") + public void sendDeliveryReceipt(@Auth Account source, + @PathParam("destination") String destination, + @PathParam("messageId") long messageId, + @QueryParam("relay") Optional relay) + throws IOException + { + try { + if (relay.isPresent()) sendRelayedReceipt(source, destination, messageId, relay.get()); + else sendDirectReceipt(source, destination, messageId); + } catch (NoSuchUserException | NotPushRegisteredException e) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } catch (TransientPushFailureException e) { + throw new IOException(e); + } + } + + private void sendRelayedReceipt(Account source, String destination, long messageId, String relay) + throws NoSuchUserException, IOException + { + try { + federatedClientManager.getClient(relay) + .sendDeliveryReceipt(source.getNumber(), + source.getAuthenticatedDevice().get().getId(), + destination, messageId); + } catch (NoSuchPeerException e) { + throw new NoSuchUserException(e); + } + } + + private void sendDirectReceipt(Account source, String destination, long messageId) + throws NotPushRegisteredException, TransientPushFailureException, NoSuchUserException + { + Account destinationAccount = getDestinationAccount(destination); + List destinationDevices = destinationAccount.getDevices(); + + OutgoingMessageSignal.Builder message = + OutgoingMessageSignal.newBuilder() + .setSource(source.getNumber()) + .setSourceDevice((int) source.getAuthenticatedDevice().get().getId()) + .setTimestamp(messageId) + .setType(OutgoingMessageSignal.Type.RECEIPT_VALUE); + + if (source.getRelay().isPresent()) { + message.setRelay(source.getRelay().get()); + } + + for (Device destinationDevice : destinationDevices) { + pushSender.sendMessage(destinationAccount, destinationDevice, message.build()); + } + } + + private Account getDestinationAccount(String destination) + throws NoSuchUserException + { + Optional account = accountManager.get(destination); + + if (!account.isPresent()) { + throw new NoSuchUserException(destination); + } + + return account.get(); + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java index 978cf7666..8a300fd47 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java @@ -16,11 +16,13 @@ import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PushSender; import org.whispersystems.textsecuregcm.push.TransientPushFailureException; import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.PubSubListener; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.PubSubMessage; import org.whispersystems.textsecuregcm.storage.StoredMessages; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; import org.whispersystems.textsecuregcm.websocket.WebsocketMessage; @@ -33,14 +35,16 @@ import java.util.Map; import io.dropwizard.auth.AuthenticationException; import io.dropwizard.auth.basic.BasicCredentials; +import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal; public class WebsocketController implements WebSocketListener, PubSubListener { private static final Logger logger = LoggerFactory.getLogger(WebsocketController.class); - private static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = SystemMapper.getMapper(); private static final Map pendingMessages = new HashMap<>(); private final AccountAuthenticator accountAuthenticator; + private final AccountsManager accountsManager; private final PubSubManager pubSubManager; private final StoredMessages storedMessages; private final PushSender pushSender; @@ -53,11 +57,13 @@ public class WebsocketController implements WebSocketListener, PubSubListener { private long pendingMessageSequence; public WebsocketController(AccountAuthenticator accountAuthenticator, + AccountsManager accountsManager, PushSender pushSender, PubSubManager pubSubManager, StoredMessages storedMessages) { this.accountAuthenticator = accountAuthenticator; + this.accountsManager = accountsManager; this.pushSender = pushSender; this.pubSubManager = pubSubManager; this.storedMessages = storedMessages; @@ -186,10 +192,16 @@ public class WebsocketController implements WebSocketListener, PubSubListener { private void handleMessageAck(String message) { try { AcknowledgeWebsocketMessage ack = mapper.readValue(message, AcknowledgeWebsocketMessage.class); + PendingMessage acknowledgedMessage; synchronized (pendingMessages) { - pendingMessages.remove(ack.getId()); + acknowledgedMessage = pendingMessages.remove(ack.getId()); } + + if (acknowledgedMessage != null && !acknowledgedMessage.isReceipt()) { + sendDeliveryReceipt(acknowledgedMessage); + } + } catch (IOException e) { logger.warn("Mapping", e); } @@ -203,6 +215,30 @@ public class WebsocketController implements WebSocketListener, PubSubListener { } } + private void sendDeliveryReceipt(PendingMessage acknowledgedMessage) { + try { + Optional source = accountsManager.get(acknowledgedMessage.getSender()); + + if (!source.isPresent()) { + logger.warn("Source account disappeared? (%s)", acknowledgedMessage.getSender()); + return; + } + + OutgoingMessageSignal.Builder receipt = + OutgoingMessageSignal.newBuilder() + .setSource(account.getNumber()) + .setSourceDevice((int) device.getId()) + .setTimestamp(acknowledgedMessage.getMessageId()) + .setType(OutgoingMessageSignal.Type.RECEIPT_VALUE); + + for (Device device : source.get().getDevices()) { + pushSender.sendMessage(source.get(), device, receipt.build()); + } + } catch (NotPushRegisteredException | TransientPushFailureException e) { + logger.warn("Websocket", "Delivery receipet", e); + } + } + @Override public void onWebSocketBinary(byte[] bytes, int i, int i2) { logger.info("Received binary message!"); diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java index 1a5c6bd66..7f4c42168 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java @@ -41,7 +41,7 @@ public class IncomingMessage { private String relay; @JsonProperty - private long timestamp; + private long timestamp; // deprecated public String getDestination() { diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java index d9c9b6dd3..755744bda 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java @@ -32,6 +32,9 @@ public class IncomingMessageList { @JsonProperty private String relay; + @JsonProperty + private long timestamp; + public IncomingMessageList() {} public List getMessages() { @@ -45,4 +48,8 @@ public class IncomingMessageList { public void setRelay(String relay) { this.relay = relay; } + + public long getTimestamp() { + return timestamp; + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java b/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java index 989902c40..24ae9daba 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java @@ -10,495 +10,133 @@ public final class MessageProtos { } public interface OutgoingMessageSignalOrBuilder extends com.google.protobuf.MessageOrBuilder { - + // optional uint32 type = 1; + /** + * optional uint32 type = 1; + */ boolean hasType(); + /** + * optional uint32 type = 1; + */ int getType(); - + // optional string source = 2; + /** + * optional string source = 2; + */ boolean hasSource(); - String getSource(); - + /** + * optional string source = 2; + */ + java.lang.String getSource(); + /** + * optional string source = 2; + */ + com.google.protobuf.ByteString + getSourceBytes(); + // optional uint32 sourceDevice = 7; + /** + * optional uint32 sourceDevice = 7; + */ boolean hasSourceDevice(); + /** + * optional uint32 sourceDevice = 7; + */ int getSourceDevice(); - + // optional string relay = 3; + /** + * optional string relay = 3; + */ boolean hasRelay(); - String getRelay(); - + /** + * optional string relay = 3; + */ + java.lang.String getRelay(); + /** + * optional string relay = 3; + */ + com.google.protobuf.ByteString + getRelayBytes(); + // optional uint64 timestamp = 5; + /** + * optional uint64 timestamp = 5; + * + *
+     *  repeated string destinations = 4;
+     * 
+ */ boolean hasTimestamp(); + /** + * optional uint64 timestamp = 5; + * + *
+     *  repeated string destinations = 4;
+     * 
+ */ long getTimestamp(); - + // optional bytes message = 6; + /** + * optional bytes message = 6; + */ boolean hasMessage(); + /** + * optional bytes message = 6; + */ com.google.protobuf.ByteString getMessage(); } + /** + * Protobuf type {@code textsecure.OutgoingMessageSignal} + */ public static final class OutgoingMessageSignal extends com.google.protobuf.GeneratedMessage implements OutgoingMessageSignalOrBuilder { // Use OutgoingMessageSignal.newBuilder() to construct. - private OutgoingMessageSignal(Builder builder) { + private OutgoingMessageSignal(com.google.protobuf.GeneratedMessage.Builder builder) { super(builder); + this.unknownFields = builder.getUnknownFields(); } - private OutgoingMessageSignal(boolean noInit) {} - + private OutgoingMessageSignal(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + private static final OutgoingMessageSignal defaultInstance; public static OutgoingMessageSignal getDefaultInstance() { return defaultInstance; } - + public OutgoingMessageSignal getDefaultInstanceForType() { return defaultInstance; } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable; - } - - private int bitField0_; - // optional uint32 type = 1; - public static final int TYPE_FIELD_NUMBER = 1; - private int type_; - public boolean hasType() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - public int getType() { - return type_; - } - - // optional string source = 2; - public static final int SOURCE_FIELD_NUMBER = 2; - private java.lang.Object source_; - public boolean hasSource() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - public String getSource() { - java.lang.Object ref = source_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - if (com.google.protobuf.Internal.isValidUtf8(bs)) { - source_ = s; - } - return s; - } - } - private com.google.protobuf.ByteString getSourceBytes() { - java.lang.Object ref = source_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((String) ref); - source_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional uint32 sourceDevice = 7; - public static final int SOURCEDEVICE_FIELD_NUMBER = 7; - private int sourceDevice_; - public boolean hasSourceDevice() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - public int getSourceDevice() { - return sourceDevice_; - } - - // optional string relay = 3; - public static final int RELAY_FIELD_NUMBER = 3; - private java.lang.Object relay_; - public boolean hasRelay() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - public String getRelay() { - java.lang.Object ref = relay_; - if (ref instanceof String) { - return (String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); - if (com.google.protobuf.Internal.isValidUtf8(bs)) { - relay_ = s; - } - return s; - } - } - private com.google.protobuf.ByteString getRelayBytes() { - java.lang.Object ref = relay_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((String) ref); - relay_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional uint64 timestamp = 5; - public static final int TIMESTAMP_FIELD_NUMBER = 5; - private long timestamp_; - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - public long getTimestamp() { - return timestamp_; - } - - // optional bytes message = 6; - public static final int MESSAGE_FIELD_NUMBER = 6; - private com.google.protobuf.ByteString message_; - public boolean hasMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - public com.google.protobuf.ByteString getMessage() { - return message_; - } - - private void initFields() { - type_ = 0; - source_ = ""; - sourceDevice_ = 0; - relay_ = ""; - timestamp_ = 0L; - message_ = com.google.protobuf.ByteString.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeUInt32(1, type_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getSourceBytes()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(3, getRelayBytes()); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeUInt64(5, timestamp_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBytes(6, message_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeUInt32(7, sourceDevice_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(1, type_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getSourceBytes()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, getRelayBytes()); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(5, timestamp_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(6, message_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(7, sourceDevice_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; + + private final com.google.protobuf.UnknownFieldSet unknownFields; @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; } - - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data).buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data).buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return newBuilder().mergeFrom(data, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(java.io.InputStream input) - throws java.io.IOException { - return newBuilder().mergeFrom(input).buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return newBuilder().mergeFrom(input, extensionRegistry) - .buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - Builder builder = newBuilder(); - if (builder.mergeDelimitedFrom(input)) { - return builder.buildParsed(); - } else { - return null; - } - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - Builder builder = newBuilder(); - if (builder.mergeDelimitedFrom(input, extensionRegistry)) { - return builder.buildParsed(); - } else { - return null; - } - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return newBuilder().mergeFrom(input).buildParsed(); - } - public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + private OutgoingMessageSignal( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return newBuilder().mergeFrom(input, extensionRegistry) - .buildParsed(); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignalOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable; - } - - // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder(BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - type_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); - source_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - sourceDevice_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - relay_ = ""; - bitField0_ = (bitField0_ & ~0x00000008); - timestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000010); - message_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000020); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDescriptor(); - } - - public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal getDefaultInstanceForType() { - return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance(); - } - - public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal build() { - org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - private org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal buildParsed() - throws com.google.protobuf.InvalidProtocolBufferException { - org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException( - result).asInvalidProtocolBufferException(); - } - return result; - } - - public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal buildPartial() { - org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = new org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.type_ = type_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.source_ = source_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.sourceDevice_ = sourceDevice_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.relay_ = relay_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.timestamp_ = timestamp_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.message_ = message_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal) { - return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal other) { - if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance()) return this; - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasSource()) { - setSource(other.getSource()); - } - if (other.hasSourceDevice()) { - setSourceDevice(other.getSourceDevice()); - } - if (other.hasRelay()) { - setRelay(other.getRelay()); - } - if (other.hasTimestamp()) { - setTimestamp(other.getTimestamp()); - } - if (other.hasMessage()) { - setMessage(other.getMessage()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder( - this.getUnknownFields()); - while (true) { + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { int tag = input.readTag(); switch (tag) { case 0: - this.setUnknownFields(unknownFields.build()); - onChanged(); - return this; + done = true; + break; default: { if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - this.setUnknownFields(unknownFields.build()); - onChanged(); - return this; + done = true; } break; } @@ -534,47 +172,712 @@ public final class MessageProtos { } } } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.class, org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public OutgoingMessageSignal parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new OutgoingMessageSignal(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + /** + * Protobuf enum {@code textsecure.OutgoingMessageSignal.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * UNKNOWN = 0; + */ + UNKNOWN(0, 0), + /** + * CIPHERTEXT = 1; + */ + CIPHERTEXT(1, 1), + /** + * KEY_EXCHANGE = 2; + */ + KEY_EXCHANGE(2, 2), + /** + * PREKEY_BUNDLE = 3; + */ + PREKEY_BUNDLE(3, 3), + /** + * PLAINTEXT = 4; + */ + PLAINTEXT(4, 4), + /** + * RECEIPT = 5; + */ + RECEIPT(5, 5), + ; + + /** + * UNKNOWN = 0; + */ + public static final int UNKNOWN_VALUE = 0; + /** + * CIPHERTEXT = 1; + */ + public static final int CIPHERTEXT_VALUE = 1; + /** + * KEY_EXCHANGE = 2; + */ + public static final int KEY_EXCHANGE_VALUE = 2; + /** + * PREKEY_BUNDLE = 3; + */ + public static final int PREKEY_BUNDLE_VALUE = 3; + /** + * PLAINTEXT = 4; + */ + public static final int PLAINTEXT_VALUE = 4; + /** + * RECEIPT = 5; + */ + public static final int RECEIPT_VALUE = 5; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 0: return UNKNOWN; + case 1: return CIPHERTEXT; + case 2: return KEY_EXCHANGE; + case 3: return PREKEY_BUNDLE; + case 4: return PLAINTEXT; + case 5: return RECEIPT; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:textsecure.OutgoingMessageSignal.Type) + } + + private int bitField0_; + // optional uint32 type = 1; + public static final int TYPE_FIELD_NUMBER = 1; + private int type_; + /** + * optional uint32 type = 1; + */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional uint32 type = 1; + */ + public int getType() { + return type_; + } + + // optional string source = 2; + public static final int SOURCE_FIELD_NUMBER = 2; + private java.lang.Object source_; + /** + * optional string source = 2; + */ + public boolean hasSource() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string source = 2; + */ + public java.lang.String getSource() { + java.lang.Object ref = source_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + source_ = s; + } + return s; + } + } + /** + * optional string source = 2; + */ + public com.google.protobuf.ByteString + getSourceBytes() { + java.lang.Object ref = source_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + source_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional uint32 sourceDevice = 7; + public static final int SOURCEDEVICE_FIELD_NUMBER = 7; + private int sourceDevice_; + /** + * optional uint32 sourceDevice = 7; + */ + public boolean hasSourceDevice() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional uint32 sourceDevice = 7; + */ + public int getSourceDevice() { + return sourceDevice_; + } + + // optional string relay = 3; + public static final int RELAY_FIELD_NUMBER = 3; + private java.lang.Object relay_; + /** + * optional string relay = 3; + */ + public boolean hasRelay() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional string relay = 3; + */ + public java.lang.String getRelay() { + java.lang.Object ref = relay_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + relay_ = s; + } + return s; + } + } + /** + * optional string relay = 3; + */ + public com.google.protobuf.ByteString + getRelayBytes() { + java.lang.Object ref = relay_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + relay_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional uint64 timestamp = 5; + public static final int TIMESTAMP_FIELD_NUMBER = 5; + private long timestamp_; + /** + * optional uint64 timestamp = 5; + * + *
+     *  repeated string destinations = 4;
+     * 
+ */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional uint64 timestamp = 5; + * + *
+     *  repeated string destinations = 4;
+     * 
+ */ + public long getTimestamp() { + return timestamp_; + } + + // optional bytes message = 6; + public static final int MESSAGE_FIELD_NUMBER = 6; + private com.google.protobuf.ByteString message_; + /** + * optional bytes message = 6; + */ + public boolean hasMessage() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes message = 6; + */ + public com.google.protobuf.ByteString getMessage() { + return message_; + } + + private void initFields() { + type_ = 0; + source_ = ""; + sourceDevice_ = 0; + relay_ = ""; + timestamp_ = 0L; + message_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, type_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getSourceBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(3, getRelayBytes()); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeUInt64(5, timestamp_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(6, message_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(7, sourceDevice_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, type_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getSourceBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getRelayBytes()); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(5, timestamp_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, message_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(7, sourceDevice_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code textsecure.OutgoingMessageSignal} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignalOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.class, org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + type_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + source_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + sourceDevice_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + relay_ = ""; + bitField0_ = (bitField0_ & ~0x00000008); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000010); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = new org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.source_ = source_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.sourceDevice_ = sourceDevice_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.relay_ = relay_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.message_ = message_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasSource()) { + bitField0_ |= 0x00000002; + source_ = other.source_; + onChanged(); + } + if (other.hasSourceDevice()) { + setSourceDevice(other.getSourceDevice()); + } + if (other.hasRelay()) { + bitField0_ |= 0x00000008; + relay_ = other.relay_; + onChanged(); + } + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + if (other.hasMessage()) { + setMessage(other.getMessage()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; } - private int bitField0_; - + // optional uint32 type = 1; private int type_ ; + /** + * optional uint32 type = 1; + */ public boolean hasType() { return ((bitField0_ & 0x00000001) == 0x00000001); } + /** + * optional uint32 type = 1; + */ public int getType() { return type_; } + /** + * optional uint32 type = 1; + */ public Builder setType(int value) { bitField0_ |= 0x00000001; type_ = value; onChanged(); return this; } + /** + * optional uint32 type = 1; + */ public Builder clearType() { bitField0_ = (bitField0_ & ~0x00000001); type_ = 0; onChanged(); return this; } - + // optional string source = 2; private java.lang.Object source_ = ""; + /** + * optional string source = 2; + */ public boolean hasSource() { return ((bitField0_ & 0x00000002) == 0x00000002); } - public String getSource() { + /** + * optional string source = 2; + */ + public java.lang.String getSource() { java.lang.Object ref = source_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); source_ = s; return s; } else { - return (String) ref; + return (java.lang.String) ref; } } - public Builder setSource(String value) { + /** + * optional string source = 2; + */ + public com.google.protobuf.ByteString + getSourceBytes() { + java.lang.Object ref = source_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + source_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string source = 2; + */ + public Builder setSource( + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -583,55 +886,105 @@ public final class MessageProtos { onChanged(); return this; } + /** + * optional string source = 2; + */ public Builder clearSource() { bitField0_ = (bitField0_ & ~0x00000002); source_ = getDefaultInstance().getSource(); onChanged(); return this; } - void setSource(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000002; + /** + * optional string source = 2; + */ + public Builder setSourceBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; source_ = value; onChanged(); + return this; } - + // optional uint32 sourceDevice = 7; private int sourceDevice_ ; + /** + * optional uint32 sourceDevice = 7; + */ public boolean hasSourceDevice() { return ((bitField0_ & 0x00000004) == 0x00000004); } + /** + * optional uint32 sourceDevice = 7; + */ public int getSourceDevice() { return sourceDevice_; } + /** + * optional uint32 sourceDevice = 7; + */ public Builder setSourceDevice(int value) { bitField0_ |= 0x00000004; sourceDevice_ = value; onChanged(); return this; } + /** + * optional uint32 sourceDevice = 7; + */ public Builder clearSourceDevice() { bitField0_ = (bitField0_ & ~0x00000004); sourceDevice_ = 0; onChanged(); return this; } - + // optional string relay = 3; private java.lang.Object relay_ = ""; + /** + * optional string relay = 3; + */ public boolean hasRelay() { return ((bitField0_ & 0x00000008) == 0x00000008); } - public String getRelay() { + /** + * optional string relay = 3; + */ + public java.lang.String getRelay() { java.lang.Object ref = relay_; - if (!(ref instanceof String)) { - String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); relay_ = s; return s; } else { - return (String) ref; + return (java.lang.String) ref; } } - public Builder setRelay(String value) { + /** + * optional string relay = 3; + */ + public com.google.protobuf.ByteString + getRelayBytes() { + java.lang.Object ref = relay_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + relay_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string relay = 3; + */ + public Builder setRelay( + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -640,47 +993,95 @@ public final class MessageProtos { onChanged(); return this; } + /** + * optional string relay = 3; + */ public Builder clearRelay() { bitField0_ = (bitField0_ & ~0x00000008); relay_ = getDefaultInstance().getRelay(); onChanged(); return this; } - void setRelay(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000008; + /** + * optional string relay = 3; + */ + public Builder setRelayBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; relay_ = value; onChanged(); + return this; } - + // optional uint64 timestamp = 5; private long timestamp_ ; + /** + * optional uint64 timestamp = 5; + * + *
+       *  repeated string destinations = 4;
+       * 
+ */ public boolean hasTimestamp() { return ((bitField0_ & 0x00000010) == 0x00000010); } + /** + * optional uint64 timestamp = 5; + * + *
+       *  repeated string destinations = 4;
+       * 
+ */ public long getTimestamp() { return timestamp_; } + /** + * optional uint64 timestamp = 5; + * + *
+       *  repeated string destinations = 4;
+       * 
+ */ public Builder setTimestamp(long value) { bitField0_ |= 0x00000010; timestamp_ = value; onChanged(); return this; } + /** + * optional uint64 timestamp = 5; + * + *
+       *  repeated string destinations = 4;
+       * 
+ */ public Builder clearTimestamp() { bitField0_ = (bitField0_ & ~0x00000010); timestamp_ = 0L; onChanged(); return this; } - + // optional bytes message = 6; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes message = 6; + */ public boolean hasMessage() { return ((bitField0_ & 0x00000020) == 0x00000020); } + /** + * optional bytes message = 6; + */ public com.google.protobuf.ByteString getMessage() { return message_; } + /** + * optional bytes message = 6; + */ public Builder setMessage(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); @@ -690,30 +1091,33 @@ public final class MessageProtos { onChanged(); return this; } + /** + * optional bytes message = 6; + */ public Builder clearMessage() { bitField0_ = (bitField0_ & ~0x00000020); message_ = getDefaultInstance().getMessage(); onChanged(); return this; } - + // @@protoc_insertion_point(builder_scope:textsecure.OutgoingMessageSignal) } - + static { defaultInstance = new OutgoingMessageSignal(true); defaultInstance.initFields(); } - + // @@protoc_insertion_point(class_scope:textsecure.OutgoingMessageSignal) } - + private static com.google.protobuf.Descriptors.Descriptor internal_static_textsecure_OutgoingMessageSignal_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable; - + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { return descriptor; @@ -723,11 +1127,14 @@ public final class MessageProtos { static { java.lang.String[] descriptorData = { "\n\033OutgoingMessageSignal.proto\022\ntextsecur" + - "e\"~\n\025OutgoingMessageSignal\022\014\n\004type\030\001 \001(\r" + - "\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevice\030\007 \001(\r\022\r" + - "\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(\004\022\017\n\007mess" + - "age\030\006 \001(\014B:\n)org.whispersystems.textsecu" + - "regcm.entitiesB\rMessageProtos" + "e\"\344\001\n\025OutgoingMessageSignal\022\014\n\004type\030\001 \001(" + + "\r\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevice\030\007 \001(\r\022" + + "\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(\004\022\017\n\007mes" + + "sage\030\006 \001(\014\"d\n\004Type\022\013\n\007UNKNOWN\020\000\022\016\n\nCIPHE" + + "RTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n\rPREKEY_BUND" + + "LE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEIPT\020\005B:\n)org." + + "whispersystems.textsecuregcm.entitiesB\rM" + + "essageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -739,9 +1146,7 @@ public final class MessageProtos { internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_OutgoingMessageSignal_descriptor, - new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "Message", }, - org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.class, - org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.Builder.class); + new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "Message", }); return null; } }; @@ -750,6 +1155,6 @@ public final class MessageProtos { new com.google.protobuf.Descriptors.FileDescriptor[] { }, assigner); } - + // @@protoc_insertion_point(outer_class_scope) } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/PendingMessage.java b/src/main/java/org/whispersystems/textsecuregcm/entities/PendingMessage.java index abbc0fa4f..611d09c43 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/PendingMessage.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/PendingMessage.java @@ -13,11 +13,15 @@ public class PendingMessage { @JsonProperty private String encryptedOutgoingMessage; + @JsonProperty + private boolean receipt; + public PendingMessage() {} - public PendingMessage(String sender, long messageId, String encryptedOutgoingMessage) { + public PendingMessage(String sender, long messageId, boolean receipt, String encryptedOutgoingMessage) { this.sender = sender; this.messageId = messageId; + this.receipt = receipt; this.encryptedOutgoingMessage = encryptedOutgoingMessage; } @@ -33,6 +37,10 @@ public class PendingMessage { return sender; } + public boolean isReceipt() { + return receipt; + } + @Override public boolean equals(Object other) { if (other == null || !(other instanceof PendingMessage)) return false; @@ -41,11 +49,12 @@ public class PendingMessage { return this.sender.equals(that.sender) && this.messageId == that.messageId && + this.receipt == that.receipt && this.encryptedOutgoingMessage.equals(that.encryptedOutgoingMessage); } @Override public int hashCode() { - return this.sender.hashCode() ^ (int)this.messageId ^ this.encryptedOutgoingMessage.hashCode(); + return this.sender.hashCode() ^ (int)this.messageId ^ this.encryptedOutgoingMessage.hashCode() ^ (receipt ? 1 : 0); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java index 97ac15f0d..00fe477ab 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java +++ b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java @@ -69,6 +69,7 @@ public class FederatedClient { private static final String PREKEY_PATH_DEVICE_V1 = "/v1/federation/key/%s/%s"; private static final String PREKEY_PATH_DEVICE_V2 = "/v2/federation/key/%s/%s"; private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d"; + private static final String RECEIPT_PATH = "/v1/receipt/%s/%d/%s/%d"; private final FederatedPeer peer; private final Client client; @@ -197,6 +198,25 @@ public class FederatedClient { } } + public void sendDeliveryReceipt(String source, long sourceDeviceId, String destination, long messageId) + throws IOException + { + try { + String path = String.format(RECEIPT_PATH, source, sourceDeviceId, destination, messageId); + WebResource resource = client.resource(peer.getUrl()).path(path); + ClientResponse response = resource.type(MediaType.APPLICATION_JSON) + .header("Authorization", authorizationHeader) + .put(ClientResponse.class); + + if (response.getStatus() != 200 && response.getStatus() != 204) { + throw new WebApplicationException(clientResponseToResponse(response)); + } + } catch (UniformInterfaceException | ClientHandlerException e) { + logger.warn("sendMessage", e); + throw new IOException(e); + } + } + private String getAuthorizationHeader(String federationName, FederatedPeer peer) { return "Basic " + Base64.encodeBytes((federationName + ":" + peer.getAuthenticationToken()).getBytes()); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java index 01f078714..734ffd36b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java @@ -19,13 +19,13 @@ package org.whispersystems.textsecuregcm.push; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.notnoop.apns.APNS; import com.notnoop.apns.ApnsService; import com.notnoop.exceptions.NetworkIOException; import net.spy.memcached.MemcachedClient; import org.bouncycastle.openssl.PEMReader; -import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.PendingMessage; @@ -36,6 +36,7 @@ import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.PubSubMessage; import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; @@ -69,7 +70,7 @@ public class APNSender implements Managed { private static final String MESSAGE_BODY = "m"; - private static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = SystemMapper.getMapper(); private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); @@ -112,7 +113,10 @@ public class APNSender implements Managed { } else { memcacheSet(registrationId, account.getNumber()); storedMessages.insert(account.getId(), device.getId(), message); - sendPush(registrationId, serializedPendingMessage); + + if (!message.isReceipt()) { + sendPush(registrationId, serializedPendingMessage); + } } } catch (IOException e) { throw new TransientPushFailureException(e); diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java index c2b0b15c6..4dad32d3d 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java @@ -87,9 +87,11 @@ public class GCMSender implements Managed, PacketListener { public void sendMessage(String messageId, UnacknowledgedMessage message) { try { + boolean isReceipt = message.getPendingMessage().isReceipt(); + Map dataObject = new HashMap<>(); dataObject.put("type", "message"); - dataObject.put("message", message.getPendingMessage().getEncryptedOutgoingMessage()); + dataObject.put(isReceipt ? "receipt" : "message", message.getPendingMessage().getEncryptedOutgoingMessage()); Map messageObject = new HashMap<>(); messageObject.put("to", message.getRegistrationId()); diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java index fc458f3f9..74d5da008 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java @@ -25,6 +25,8 @@ import org.whispersystems.textsecuregcm.entities.PendingMessage; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; +import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal; + public class PushSender { private final Logger logger = LoggerFactory.getLogger(PushSender.class); @@ -42,13 +44,17 @@ public class PushSender { this.webSocketSender = websocketSender; } - public void sendMessage(Account account, Device device, MessageProtos.OutgoingMessageSignal message) + public void sendMessage(Account account, Device device, OutgoingMessageSignal message) throws NotPushRegisteredException, TransientPushFailureException { try { + boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE; String signalingKey = device.getSignalingKey(); EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, signalingKey); - PendingMessage pendingMessage = new PendingMessage(message.getSource(), message.getTimestamp(), encryptedMessage.serialize()); + PendingMessage pendingMessage = new PendingMessage(message.getSource(), + message.getTimestamp(), + isReceipt, + encryptedMessage.serialize()); sendMessage(account, device, pendingMessage); } catch (CryptoEncodingException e) { diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java index 612772e94..2bbc8d763 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java @@ -31,6 +31,7 @@ import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.PubSubMessage; import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; import static com.codahale.metrics.MetricRegistry.name; @@ -43,7 +44,7 @@ public class WebsocketSender { private final Meter onlineMeter = metricRegistry.meter(name(getClass(), "online")); private final Meter offlineMeter = metricRegistry.meter(name(getClass(), "offline")); - private static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = SystemMapper.getMapper(); private final StoredMessages storedMessages; private final PubSubManager pubSubManager; diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java index 62c18b69c..57cabcbb9 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java @@ -33,6 +33,7 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate; import org.skife.jdbi.v2.sqlobject.Transaction; import org.skife.jdbi.v2.sqlobject.customizers.Mapper; import org.skife.jdbi.v2.tweak.ResultSetMapper; +import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; import java.lang.annotation.Annotation; @@ -51,12 +52,7 @@ public abstract class Accounts { private static final String NUMBER = "number"; private static final String DATA = "data"; - private static final ObjectMapper mapper = new ObjectMapper(); - - static { - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - } + private static final ObjectMapper mapper = SystemMapper.getMapper(); @SqlUpdate("INSERT INTO accounts (" + NUMBER + ", " + DATA + ") VALUES (:number, CAST(:data AS json))") @GetGeneratedKeys diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 60b45b789..bb0f4e275 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -17,6 +17,8 @@ package org.whispersystems.textsecuregcm.storage; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; @@ -24,6 +26,7 @@ import net.spy.memcached.MemcachedClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; @@ -46,7 +49,7 @@ public class AccountsManager { this.accounts = accounts; this.directory = directory; this.memcachedClient = memcachedClient; - this.mapper = new ObjectMapper(); + this.mapper = SystemMapper.getMapper(); } public long getCount() { diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java index 471d892ce..4ebb4e841 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; @@ -18,7 +19,7 @@ import redis.clients.jedis.JedisPubSub; public class PubSubManager { private final Logger logger = LoggerFactory.getLogger(PubSubManager.class); - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = SystemMapper.getMapper(); private final SubscriptionListener baseListener = new SubscriptionListener(); private final Map listeners = new HashMap<>(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/StoredMessages.java b/src/main/java/org/whispersystems/textsecuregcm/storage/StoredMessages.java index 3a52da634..b40b170a2 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/StoredMessages.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/StoredMessages.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.PendingMessage; import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; import java.util.LinkedList; @@ -43,7 +44,7 @@ public class StoredMessages { private final Histogram queueSizeHistogram = metricRegistry.histogram(name(getClass(), "queue_size")); - private static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = SystemMapper.getMapper(); private static final String QUEUE_PREFIX = "msgs"; private final JedisPool jedisPool; diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/CORSHeaderFilter.java b/src/main/java/org/whispersystems/textsecuregcm/util/CORSHeaderFilter.java deleted file mode 100644 index e193dd755..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/util/CORSHeaderFilter.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (C) 2014 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.util; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class CORSHeaderFilter implements Filter { - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (response instanceof HttpServletResponse) { - ((HttpServletResponse) response).addHeader("Access-Control-Allow-Origin", "*"); - ((HttpServletResponse) response).addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); - ((HttpServletResponse) response).addHeader("Access-Control-Allow-Headers", "Authorization, Content-type"); - } - chain.doFilter(request, response); - } - - @Override public void init(FilterConfig filterConfig) throws ServletException { } - @Override public void destroy() { } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java b/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java new file mode 100644 index 000000000..9311f98f0 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java @@ -0,0 +1,20 @@ +package org.whispersystems.textsecuregcm.util; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class SystemMapper { + + private static final ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + } + + public static ObjectMapper getMapper() { + return mapper; + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebsocketControllerFactory.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebsocketControllerFactory.java index 8c7c5c450..364d4788f 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebsocketControllerFactory.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebsocketControllerFactory.java @@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.controllers.WebsocketController; import org.whispersystems.textsecuregcm.push.PushSender; -import org.whispersystems.textsecuregcm.push.WebsocketSender; +import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; @@ -23,13 +23,16 @@ public class WebsocketControllerFactory extends WebSocketServlet implements WebS private final StoredMessages storedMessages; private final PubSubManager pubSubManager; private final AccountAuthenticator accountAuthenticator; + private final AccountsManager accounts; public WebsocketControllerFactory(AccountAuthenticator accountAuthenticator, + AccountsManager accounts, PushSender pushSender, StoredMessages storedMessages, PubSubManager pubSubManager) { this.accountAuthenticator = accountAuthenticator; + this.accounts = accounts; this.pushSender = pushSender; this.storedMessages = storedMessages; this.pubSubManager = pubSubManager; @@ -42,6 +45,6 @@ public class WebsocketControllerFactory extends WebSocketServlet implements WebS @Override public Object createWebSocket(UpgradeRequest upgradeRequest, UpgradeResponse upgradeResponse) { - return new WebsocketController(accountAuthenticator, pushSender, pubSubManager, storedMessages); + return new WebsocketController(accountAuthenticator, accounts, pushSender, pubSubManager, storedMessages); } } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java new file mode 100644 index 000000000..b199c0103 --- /dev/null +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java @@ -0,0 +1,91 @@ +package org.whispersystems.textsecuregcm.tests.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Optional; +import com.sun.jersey.api.client.ClientResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.whispersystems.textsecuregcm.controllers.MessageController; +import org.whispersystems.textsecuregcm.controllers.ReceiptController; +import org.whispersystems.textsecuregcm.entities.MessageProtos; +import org.whispersystems.textsecuregcm.federation.FederatedClientManager; +import org.whispersystems.textsecuregcm.limits.RateLimiter; +import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.textsecuregcm.push.PushSender; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.tests.util.AuthHelper; + +import java.util.LinkedList; +import java.util.List; + +import io.dropwizard.testing.junit.ResourceTestRule; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +public class ReceiptControllerTest { + + private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111"; + private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; + + private final PushSender pushSender = mock(PushSender.class ); + private final FederatedClientManager federatedClientManager = mock(FederatedClientManager.class); + private final AccountsManager accountsManager = mock(AccountsManager.class ); + + private final ObjectMapper mapper = new ObjectMapper(); + + @Rule + public final ResourceTestRule resources = ResourceTestRule.builder() + .addProvider(AuthHelper.getAuthenticator()) + .addResource(new ReceiptController(accountsManager, federatedClientManager, pushSender)) + .build(); + + @Before + public void setup() throws Exception { + List singleDeviceList = new LinkedList() {{ + add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111, null)); + }}; + + List multiDeviceList = new LinkedList() {{ + add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222, null)); + add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333, null)); + }}; + + Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList); + Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, false, multiDeviceList); + + when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount)); + when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); + } + + @Test + public synchronized void testSingleDeviceCurrent() throws Exception { + ClientResponse response = + resources.client().resource(String.format("/v1/receipt/%s/%d", SINGLE_DEVICE_RECIPIENT, 1234)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .put(ClientResponse.class); + + assertThat(response.getStatus() == 204); + + verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); + } + + @Test + public synchronized void testMultiDeviceCurrent() throws Exception { + ClientResponse response = + resources.client().resource(String.format("/v1/receipt/%s/%d", MULTI_DEVICE_RECIPIENT, 12345)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .put(ClientResponse.class); + + assertThat(response.getStatus() == 204); + + verify(pushSender, times(2)).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); + } + + +} diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java index f1006c2fb..86a950a5a 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java @@ -11,9 +11,11 @@ import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.controllers.WebsocketController; import org.whispersystems.textsecuregcm.entities.AcknowledgeWebsocketMessage; import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage; +import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.PendingMessage; import org.whispersystems.textsecuregcm.push.PushSender; import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; @@ -40,6 +42,7 @@ public class WebsocketControllerTest { private static final StoredMessages storedMessages = mock(StoredMessages.class); private static final AccountAuthenticator accountAuthenticator = mock(AccountAuthenticator.class); + private static final AccountsManager accountsManager = mock(AccountsManager.class); private static final PubSubManager pubSubManager = mock(PubSubManager.class ); private static final Account account = mock(Account.class ); private static final Device device = mock(Device.class ); @@ -57,7 +60,7 @@ public class WebsocketControllerTest { when(session.getUpgradeRequest()).thenReturn(upgradeRequest); - WebsocketController controller = new WebsocketController(accountAuthenticator, pushSender, pubSubManager, storedMessages); + WebsocketController controller = new WebsocketController(accountAuthenticator, accountsManager, pushSender, pubSubManager, storedMessages); when(upgradeRequest.getParameterMap()).thenReturn(new HashMap() {{ put("login", new String[] {VALID_USER}); @@ -85,17 +88,30 @@ public class WebsocketControllerTest { RemoteEndpoint remote = mock(RemoteEndpoint.class); List outgoingMessages = new LinkedList() {{ - add(new PendingMessage("sender1", 1111, "first")); - add(new PendingMessage("sender1", 2222, "second")); - add(new PendingMessage("sender2", 3333, "third")); + add(new PendingMessage("sender1", 1111, false, "first")); + add(new PendingMessage("sender1", 2222, false, "second")); + add(new PendingMessage("sender2", 3333, false, "third")); }}; when(device.getId()).thenReturn(2L); when(account.getId()).thenReturn(31337L); when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device)); + when(account.getNumber()).thenReturn("+14152222222"); when(session.getRemote()).thenReturn(remote); when(session.getUpgradeRequest()).thenReturn(upgradeRequest); + final Device sender1device = mock(Device.class); + + List sender1devices = new LinkedList() {{ + add(sender1device); + }}; + + Account sender1 = mock(Account.class); + when(sender1.getDevices()).thenReturn(sender1devices); + + when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1)); + when(accountsManager.get("sender2")).thenReturn(Optional.absent()); + when(upgradeRequest.getParameterMap()).thenReturn(new HashMap() {{ put("login", new String[] {VALID_USER}); put("password", new String[] {VALID_PASSWORD}); @@ -107,7 +123,7 @@ public class WebsocketControllerTest { when(storedMessages.getMessagesForDevice(account.getId(), device.getId())) .thenReturn(outgoingMessages); - WebsocketControllerFactory factory = new WebsocketControllerFactory(accountAuthenticator, pushSender, storedMessages, pubSubManager); + WebsocketControllerFactory factory = new WebsocketControllerFactory(accountAuthenticator, accountsManager, pushSender, storedMessages, pubSubManager); WebsocketController controller = (WebsocketController) factory.createWebSocket(null, null); controller.onWebSocketConnect(session); @@ -119,12 +135,12 @@ public class WebsocketControllerTest { controller.onWebSocketClose(1000, "Closed"); List pending = new LinkedList() {{ - add(new PendingMessage("sender1", 1111, "first")); - add(new PendingMessage("sender2", 3333, "third")); + add(new PendingMessage("sender1", 1111, false, "first")); + add(new PendingMessage("sender2", 3333, false, "third")); }}; - verify(pushSender, times(2)).sendMessage(eq(account), eq(device), any(PendingMessage.class)); + verify(pushSender, times(1)).sendMessage(eq(sender1), eq(sender1device), any(MessageProtos.OutgoingMessageSignal.class)); } }