diff --git a/protobuf/Makefile b/protobuf/Makefile index a4d390191..117b66723 100644 --- a/protobuf/Makefile +++ b/protobuf/Makefile @@ -1,3 +1,3 @@ all: - protoc --java_out=../src/main/java/ OutgoingMessageSignal.proto PubSubMessage.proto StoredMessage.proto \ No newline at end of file + protoc --java_out=../src/main/java/ OutgoingMessageSignal.proto PubSubMessage.proto StoredMessage.proto diff --git a/protobuf/OutgoingMessageSignal.proto b/protobuf/OutgoingMessageSignal.proto index d04ee5ffa..15339d6b4 100644 --- a/protobuf/OutgoingMessageSignal.proto +++ b/protobuf/OutgoingMessageSignal.proto @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013 Open WhisperSystems + * Copyright (C) 2013 - 2015 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 @@ -37,3 +37,7 @@ message OutgoingMessageSignal { optional uint64 timestamp = 5; optional bytes message = 6; } + +message ProvisioningUuid { + optional string uuid = 1; +} \ No newline at end of file diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 6b3766f27..ea17a8710 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -47,7 +47,6 @@ import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge; import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge; -import org.whispersystems.textsecuregcm.metrics.JsonMetricsReporter; import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge; import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge; import org.whispersystems.textsecuregcm.providers.MemcacheHealthCheck; @@ -75,7 +74,8 @@ import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.UrlSigner; -import org.whispersystems.textsecuregcm.websocket.ConnectListener; +import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; +import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener; import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator; import org.whispersystems.textsecuregcm.workers.DirectoryCommand; import org.whispersystems.textsecuregcm.workers.VacuumCommand; @@ -190,15 +190,27 @@ public class WhisperServerService extends Applicationoptional string uuid = 1; + */ + boolean hasUuid(); + /** + * optional string uuid = 1; + */ + java.lang.String getUuid(); + /** + * optional string uuid = 1; + */ + com.google.protobuf.ByteString + getUuidBytes(); + } + /** + * Protobuf type {@code textsecure.ProvisioningUuid} + */ + public static final class ProvisioningUuid extends + com.google.protobuf.GeneratedMessage + implements ProvisioningUuidOrBuilder { + // Use ProvisioningUuid.newBuilder() to construct. + private ProvisioningUuid(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private ProvisioningUuid(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final ProvisioningUuid defaultInstance; + public static ProvisioningUuid getDefaultInstance() { + return defaultInstance; + } + + public ProvisioningUuid getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ProvisioningUuid( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + 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: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + uuid_ = input.readBytes(); + break; + } + } + } + } 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_ProvisioningUuid_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ProvisioningUuid_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public ProvisioningUuid parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ProvisioningUuid(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional string uuid = 1; + public static final int UUID_FIELD_NUMBER = 1; + private java.lang.Object uuid_; + /** + * optional string uuid = 1; + */ + public boolean hasUuid() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string uuid = 1; + */ + public java.lang.String getUuid() { + java.lang.Object ref = uuid_; + 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()) { + uuid_ = s; + } + return s; + } + } + /** + * optional string uuid = 1; + */ + public com.google.protobuf.ByteString + getUuidBytes() { + java.lang.Object ref = uuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + uuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + uuid_ = ""; + } + 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.writeBytes(1, getUuidBytes()); + } + 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 + .computeBytesSize(1, getUuidBytes()); + } + 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.ProvisioningUuid parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid 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.ProvisioningUuid parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid 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.ProvisioningUuid parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid 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.ProvisioningUuid parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid 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.ProvisioningUuid parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid 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.ProvisioningUuid 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.ProvisioningUuid} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuidOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ProvisioningUuid_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ProvisioningUuid_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.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(); + uuid_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + 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_ProvisioningUuid_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid result = new org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.uuid_ = uuid_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid.getDefaultInstance()) return this; + if (other.hasUuid()) { + bitField0_ |= 0x00000001; + uuid_ = other.uuid_; + onChanged(); + } + 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.ProvisioningUuid parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional string uuid = 1; + private java.lang.Object uuid_ = ""; + /** + * optional string uuid = 1; + */ + public boolean hasUuid() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string uuid = 1; + */ + public java.lang.String getUuid() { + java.lang.Object ref = uuid_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + uuid_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string uuid = 1; + */ + public com.google.protobuf.ByteString + getUuidBytes() { + java.lang.Object ref = uuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + uuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string uuid = 1; + */ + public Builder setUuid( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + uuid_ = value; + onChanged(); + return this; + } + /** + * optional string uuid = 1; + */ + public Builder clearUuid() { + bitField0_ = (bitField0_ & ~0x00000001); + uuid_ = getDefaultInstance().getUuid(); + onChanged(); + return this; + } + /** + * optional string uuid = 1; + */ + public Builder setUuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + uuid_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.ProvisioningUuid) + } + + static { + defaultInstance = new ProvisioningUuid(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.ProvisioningUuid) + } + private static com.google.protobuf.Descriptors.Descriptor internal_static_textsecure_OutgoingMessageSignal_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_ProvisioningUuid_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_ProvisioningUuid_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -1132,9 +1608,10 @@ public final class MessageProtos { "\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" + "LE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEIPT\020\005\" \n\020Prov" + + "isioningUuid\022\014\n\004uuid\030\001 \001(\tB:\n)org.whispe" + + "rsystems.textsecuregcm.entitiesB\rMessage" + + "Protos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1147,6 +1624,12 @@ public final class MessageProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_OutgoingMessageSignal_descriptor, new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "Message", }); + internal_static_textsecure_ProvisioningUuid_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_ProvisioningUuid_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_ProvisioningUuid_descriptor, + new java.lang.String[] { "Uuid", }); return null; } }; diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/ProvisioningMessage.java b/src/main/java/org/whispersystems/textsecuregcm/entities/ProvisioningMessage.java new file mode 100644 index 000000000..c6a5def96 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/ProvisioningMessage.java @@ -0,0 +1,19 @@ +package org.whispersystems.textsecuregcm.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ProvisioningMessage { + + @JsonProperty + private String body; + + public ProvisioningMessage() {} + + public ProvisioningMessage(String body) { + this.body = body; + } + + public String getBody() { + return body; + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/mappers/InvalidWebsocketAddressExceptionMapper.java b/src/main/java/org/whispersystems/textsecuregcm/mappers/InvalidWebsocketAddressExceptionMapper.java new file mode 100644 index 000000000..e47fc49d0 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/mappers/InvalidWebsocketAddressExceptionMapper.java @@ -0,0 +1,15 @@ +package org.whispersystems.textsecuregcm.mappers; + +import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class InvalidWebsocketAddressExceptionMapper implements ExceptionMapper { + @Override + public Response toResponse(InvalidWebsocketAddressException exception) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java index 07717bbb7..74f5b950d 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java @@ -50,6 +50,10 @@ public class PushSender { else throw new NotPushRegisteredException("No delivery possible!"); } + public WebsocketSender getWebSocketSender() { + return webSocketSender; + } + private void sendGcmMessage(Account account, Device device, OutgoingMessageSignal message) throws TransientPushFailureException, NotPushRegisteredException { diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java index 64152cf21..d65f61122 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java @@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.push; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; +import com.google.protobuf.ByteString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.storage.Account; @@ -26,8 +27,11 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; +import java.io.UnsupportedEncodingException; + import static com.codahale.metrics.MetricRegistry.name; import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal; import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage; @@ -44,6 +48,9 @@ public class WebsocketSender { private final Meter apnOnlineMeter = metricRegistry.meter(name(getClass(), "apn_online" )); private final Meter apnOfflineMeter = metricRegistry.meter(name(getClass(), "apn_offline")); + private final Meter provisioningOnlineMeter = metricRegistry.meter(name(getClass(), "provisioning_online" )); + private final Meter provisioningOfflineMeter = metricRegistry.meter(name(getClass(), "provisioning_offline")); + private final StoredMessages storedMessages; private final PubSubManager pubSubManager; @@ -76,4 +83,23 @@ public class WebsocketSender { return false; } } + + public boolean sendProvisioningMessage(ProvisioningAddress address, String body) { + try { + PubSubMessage pubSubMessage = PubSubMessage.newBuilder() + .setType(PubSubMessage.Type.DELIVER) + .setContent(ByteString.copyFrom(body, "UTF-8")) + .build(); + + if (pubSubManager.publish(address, pubSubMessage)) { + provisioningOnlineMeter.mark(); + return true; + } else { + provisioningOfflineMeter.mark(); + return false; + } + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/ConnectListener.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java similarity index 89% rename from src/main/java/org/whispersystems/textsecuregcm/websocket/ConnectListener.java rename to src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java index c56a2cc6a..dbb8edbf8 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/ConnectListener.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java @@ -12,7 +12,7 @@ import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.websocket.session.WebSocketSessionContext; import org.whispersystems.websocket.setup.WebSocketConnectListener; -public class ConnectListener implements WebSocketConnectListener { +public class AuthenticatedConnectListener implements WebSocketConnectListener { private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class); @@ -21,8 +21,8 @@ public class ConnectListener implements WebSocketConnectListener { private final StoredMessages storedMessages; private final PubSubManager pubSubManager; - public ConnectListener(AccountsManager accountsManager, PushSender pushSender, - StoredMessages storedMessages, PubSubManager pubSubManager) + public AuthenticatedConnectListener(AccountsManager accountsManager, PushSender pushSender, + StoredMessages storedMessages, PubSubManager pubSubManager) { this.accountsManager = accountsManager; this.pushSender = pushSender; diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningAddress.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningAddress.java new file mode 100644 index 000000000..174ffecc1 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningAddress.java @@ -0,0 +1,38 @@ +package org.whispersystems.textsecuregcm.websocket; + +import org.whispersystems.textsecuregcm.util.Base64; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public class ProvisioningAddress extends WebsocketAddress { + + private static final String PREFIX = ">>ephemeral-"; + + private final String address; + + public ProvisioningAddress(String address) throws InvalidWebsocketAddressException { + super(address, 0); + this.address = address; + + if (address == null || !address.startsWith(PREFIX)) { + throw new InvalidWebsocketAddressException(address); + } + } + + public String getAddress() { + return address; + } + + public static ProvisioningAddress generate() { + try { + byte[] random = new byte[16]; + SecureRandom.getInstance("SHA1PRNG").nextBytes(random); + + return new ProvisioningAddress(PREFIX + Base64.encodeBytesWithoutPadding(random) + .replace('+', '-').replace('/', '_')); + } catch (NoSuchAlgorithmException | InvalidWebsocketAddressException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnectListener.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnectListener.java new file mode 100644 index 000000000..405a86250 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnectListener.java @@ -0,0 +1,27 @@ +package org.whispersystems.textsecuregcm.websocket; + +import org.whispersystems.textsecuregcm.storage.PubSubManager; +import org.whispersystems.websocket.session.WebSocketSessionContext; +import org.whispersystems.websocket.setup.WebSocketConnectListener; + +public class ProvisioningConnectListener implements WebSocketConnectListener { + + private final PubSubManager pubSubManager; + + public ProvisioningConnectListener(PubSubManager pubSubManager) { + this.pubSubManager = pubSubManager; + } + + @Override + public void onWebSocketConnect(WebSocketSessionContext context) { + final ProvisioningConnection connection = new ProvisioningConnection(pubSubManager, context.getClient()); + connection.onConnected(); + + context.addListener(new WebSocketSessionContext.WebSocketEventListener() { + @Override + public void onWebSocketClose(WebSocketSessionContext context, int statusCode, String reason) { + connection.onConnectionLost(); + } + }); + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java new file mode 100644 index 000000000..817758304 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java @@ -0,0 +1,61 @@ +package org.whispersystems.textsecuregcm.websocket; + +import com.google.common.base.Optional; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid; +import org.whispersystems.textsecuregcm.storage.PubSubListener; +import org.whispersystems.textsecuregcm.storage.PubSubManager; +import org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage; +import org.whispersystems.websocket.WebSocketClient; +import org.whispersystems.websocket.messages.WebSocketResponseMessage; + +public class ProvisioningConnection implements PubSubListener { + + private final PubSubManager pubSubManager; + private final ProvisioningAddress provisioningAddress; + private final WebSocketClient client; + + public ProvisioningConnection(PubSubManager pubSubManager, WebSocketClient client) { + this.pubSubManager = pubSubManager; + this.client = client; + this.provisioningAddress = ProvisioningAddress.generate(); + } + + @Override + public void onPubSubMessage(PubSubMessage outgoingMessage) { + if (outgoingMessage.getType() == PubSubMessage.Type.DELIVER) { + Optional body = Optional.of(outgoingMessage.getContent().toByteArray()); + + ListenableFuture response = client.sendRequest("PUT", "/v1/message", body); + + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(WebSocketResponseMessage webSocketResponseMessage) { + pubSubManager.unsubscribe(provisioningAddress, ProvisioningConnection.this); + client.close(1001, "All you get."); + } + + @Override + public void onFailure(Throwable throwable) { + pubSubManager.unsubscribe(provisioningAddress, ProvisioningConnection.this); + client.close(1001, "That's all!"); + } + }); + } + } + + public void onConnected() { + this.pubSubManager.subscribe(provisioningAddress, this); + this.client.sendRequest("PUT", "/v1/address", Optional.of(ProvisioningUuid.newBuilder() + .setUuid(provisioningAddress.getAddress()) + .build() + .toByteArray())); + } + + public void onConnectionLost() { + this.pubSubManager.unsubscribe(provisioningAddress, this); + this.client.close(1001, "Done"); + } +} diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java index 3729dec40..be925c948 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java @@ -15,7 +15,7 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; import org.whispersystems.textsecuregcm.util.Base64; -import org.whispersystems.textsecuregcm.websocket.ConnectListener; +import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator; import org.whispersystems.textsecuregcm.websocket.WebSocketConnection; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; @@ -58,7 +58,7 @@ public class WebSocketConnectionTest { public void testCredentials() throws Exception { StoredMessages storedMessages = mock(StoredMessages.class); WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator); - ConnectListener connectListener = new ConnectListener(accountsManager, pushSender, storedMessages, pubSubManager); + AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(accountsManager, pushSender, storedMessages, pubSubManager); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD))))