mirror of
https://github.com/signalapp/Signal-Server.git
synced 2025-12-17 02:10:36 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c1c80dad3 | ||
|
|
32c0712715 | ||
|
|
fa4e492d1c | ||
|
|
4711fa2a9a | ||
|
|
08291502eb | ||
|
|
1f0acd0622 | ||
|
|
e88b732715 | ||
|
|
dafda85c36 | ||
|
|
8441fa9687 | ||
|
|
77800dfb01 | ||
|
|
41d15b738b | ||
|
|
aa2a5ff929 | ||
|
|
56d3c1e73f | ||
|
|
f401f9a674 | ||
|
|
30933d792b | ||
|
|
905717977e | ||
|
|
b802994809 | ||
|
|
ac96f906b3 | ||
|
|
cc395e914f | ||
|
|
f8063f8faf | ||
|
|
958ada9110 | ||
|
|
3452ea29b8 | ||
|
|
675b6f4b5e | ||
|
|
4fab67b0f5 |
26
pom.xml
26
pom.xml
@@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
<groupId>org.whispersystems.textsecure</groupId>
|
<groupId>org.whispersystems.textsecure</groupId>
|
||||||
<artifactId>TextSecureServer</artifactId>
|
<artifactId>TextSecureServer</artifactId>
|
||||||
<version>0.24</version>
|
<version>0.29</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<dropwizard.version>0.7.0</dropwizard.version>
|
<dropwizard.version>0.7.1</dropwizard.version>
|
||||||
<jackson.api.version>2.3.3</jackson.api.version>
|
<jackson.api.version>2.3.3</jackson.api.version>
|
||||||
<commons-codec.version>1.6</commons-codec.version>
|
<commons-codec.version>1.6</commons-codec.version>
|
||||||
</properties>
|
</properties>
|
||||||
@@ -53,6 +53,17 @@
|
|||||||
<artifactId>dropwizard-metrics-graphite</artifactId>
|
<artifactId>dropwizard-metrics-graphite</artifactId>
|
||||||
<version>${dropwizard.version}</version>
|
<version>${dropwizard.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.dropwizard</groupId>
|
||||||
|
<artifactId>dropwizard-client</artifactId>
|
||||||
|
<version>${dropwizard.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.dcsquare</groupId>
|
||||||
|
<artifactId>dropwizard-papertrail</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.sun.jersey</groupId>
|
<groupId>com.sun.jersey</groupId>
|
||||||
@@ -80,11 +91,6 @@
|
|||||||
<artifactId>gcm-server</artifactId>
|
<artifactId>gcm-server</artifactId>
|
||||||
<version>1.0.2</version>
|
<version>1.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
<version>2.2.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.spy</groupId>
|
<groupId>net.spy</groupId>
|
||||||
<artifactId>spymemcached</artifactId>
|
<artifactId>spymemcached</artifactId>
|
||||||
@@ -110,7 +116,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
<artifactId>jedis</artifactId>
|
<artifactId>jedis</artifactId>
|
||||||
<version>2.2.1</version>
|
<version>2.6.1</version>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -131,9 +137,9 @@
|
|||||||
<version>4.0.0</version>
|
<version>4.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.whispersystems.websocket</groupId>
|
<groupId>org.whispersystems</groupId>
|
||||||
<artifactId>websocket-resources</artifactId>
|
<artifactId>websocket-resources</artifactId>
|
||||||
<version>0.1-SNAPSHOT</version>
|
<version>0.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
all:
|
all:
|
||||||
protoc --java_out=../src/main/java/ OutgoingMessageSignal.proto
|
protoc --java_out=../src/main/java/ OutgoingMessageSignal.proto PubSubMessage.proto StoredMessage.proto
|
||||||
@@ -36,4 +36,4 @@ message OutgoingMessageSignal {
|
|||||||
// repeated string destinations = 4;
|
// repeated string destinations = 4;
|
||||||
optional uint64 timestamp = 5;
|
optional uint64 timestamp = 5;
|
||||||
optional bytes message = 6;
|
optional bytes message = 6;
|
||||||
}
|
}
|
||||||
|
|||||||
32
protobuf/PubSubMessage.proto
Normal file
32
protobuf/PubSubMessage.proto
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2014 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package textsecure;
|
||||||
|
|
||||||
|
option java_package = "org.whispersystems.textsecuregcm.storage";
|
||||||
|
option java_outer_classname = "PubSubProtos";
|
||||||
|
|
||||||
|
message PubSubMessage {
|
||||||
|
enum Type {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
QUERY_DB = 1;
|
||||||
|
DELIVER = 2;
|
||||||
|
KEEPALIVE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional Type type = 1;
|
||||||
|
optional bytes content = 2;
|
||||||
|
}
|
||||||
30
protobuf/StoredMessage.proto
Normal file
30
protobuf/StoredMessage.proto
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2014 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package textsecure;
|
||||||
|
|
||||||
|
option java_package = "org.whispersystems.textsecuregcm.storage";
|
||||||
|
option java_outer_classname = "StoredMessageProtos";
|
||||||
|
|
||||||
|
message StoredMessage {
|
||||||
|
enum Type {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
MESSAGE = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional Type type = 1;
|
||||||
|
optional bytes content = 2;
|
||||||
|
}
|
||||||
@@ -17,16 +17,15 @@
|
|||||||
package org.whispersystems.textsecuregcm;
|
package org.whispersystems.textsecuregcm;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
|
||||||
import org.whispersystems.textsecuregcm.configuration.GraphiteConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.GraphiteConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.MemcacheConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.MemcacheConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.MetricsConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.MessageStoreConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.RedPhoneConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.RedPhoneConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
|
||||||
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration;
|
||||||
@@ -35,6 +34,7 @@ import javax.validation.Valid;
|
|||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import io.dropwizard.Configuration;
|
import io.dropwizard.Configuration;
|
||||||
|
import io.dropwizard.client.JerseyClientConfiguration;
|
||||||
import io.dropwizard.db.DataSourceFactory;
|
import io.dropwizard.db.DataSourceFactory;
|
||||||
|
|
||||||
public class WhisperServerConfiguration extends Configuration {
|
public class WhisperServerConfiguration extends Configuration {
|
||||||
@@ -50,7 +50,7 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private GcmConfiguration gcm;
|
private PushConfiguration push;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
@@ -65,10 +65,12 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private RedisConfiguration redis;
|
private DirectoryConfiguration directory;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Valid
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private ApnConfiguration apn = new ApnConfiguration();
|
private MessageStoreConfiguration messageStore;
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@@ -88,10 +90,6 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private GraphiteConfiguration graphite = new GraphiteConfiguration();
|
private GraphiteConfiguration graphite = new GraphiteConfiguration();
|
||||||
|
|
||||||
@Valid
|
|
||||||
@JsonProperty
|
|
||||||
private MetricsConfiguration viz = new MetricsConfiguration();
|
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private WebsocketConfiguration websocket = new WebsocketConfiguration();
|
private WebsocketConfiguration websocket = new WebsocketConfiguration();
|
||||||
@@ -99,6 +97,12 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private RedPhoneConfiguration redphone = new RedPhoneConfiguration();
|
private RedPhoneConfiguration redphone = new RedPhoneConfiguration();
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty
|
||||||
|
private JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
|
||||||
|
|
||||||
|
|
||||||
public WebsocketConfiguration getWebsocketConfiguration() {
|
public WebsocketConfiguration getWebsocketConfiguration() {
|
||||||
return websocket;
|
return websocket;
|
||||||
}
|
}
|
||||||
@@ -111,12 +115,12 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
return nexmo;
|
return nexmo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GcmConfiguration getGcmConfiguration() {
|
public PushConfiguration getPushConfiguration() {
|
||||||
return gcm;
|
return push;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApnConfiguration getApnConfiguration() {
|
public JerseyClientConfiguration getJerseyClientConfiguration() {
|
||||||
return apn;
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public S3Configuration getS3Configuration() {
|
public S3Configuration getS3Configuration() {
|
||||||
@@ -127,8 +131,12 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
return memcache;
|
return memcache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RedisConfiguration getRedisConfiguration() {
|
public DirectoryConfiguration getDirectoryConfiguration() {
|
||||||
return redis;
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageStoreConfiguration getMessageStoreConfiguration() {
|
||||||
|
return messageStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataSourceFactory getDataSourceFactory() {
|
public DataSourceFactory getDataSourceFactory() {
|
||||||
@@ -147,10 +155,6 @@ public class WhisperServerConfiguration extends Configuration {
|
|||||||
return graphite;
|
return graphite;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetricsConfiguration getMetricsConfiguration() {
|
|
||||||
return viz;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RedPhoneConfiguration getRedphoneConfiguration() {
|
public RedPhoneConfiguration getRedphoneConfiguration() {
|
||||||
return redphone;
|
return redphone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.codahale.metrics.SharedMetricRegistries;
|
|||||||
import com.codahale.metrics.graphite.GraphiteReporter;
|
import com.codahale.metrics.graphite.GraphiteReporter;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
|
import com.sun.jersey.api.client.Client;
|
||||||
import net.spy.memcached.MemcachedClient;
|
import net.spy.memcached.MemcachedClient;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||||
@@ -34,6 +35,7 @@ import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
|||||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.FederationControllerV1;
|
import org.whispersystems.textsecuregcm.controllers.FederationControllerV1;
|
||||||
import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
|
import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
|
||||||
|
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
|
import org.whispersystems.textsecuregcm.controllers.KeysControllerV1;
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
|
import org.whispersystems.textsecuregcm.controllers.KeysControllerV2;
|
||||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||||
@@ -53,9 +55,9 @@ import org.whispersystems.textsecuregcm.providers.MemcachedClientFactory;
|
|||||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||||
import org.whispersystems.textsecuregcm.providers.RedisHealthCheck;
|
import org.whispersystems.textsecuregcm.providers.RedisHealthCheck;
|
||||||
import org.whispersystems.textsecuregcm.providers.TimeProvider;
|
import org.whispersystems.textsecuregcm.providers.TimeProvider;
|
||||||
import org.whispersystems.textsecuregcm.push.APNSender;
|
import org.whispersystems.textsecuregcm.push.FeedbackHandler;
|
||||||
import org.whispersystems.textsecuregcm.push.GCMSender;
|
|
||||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||||
|
import org.whispersystems.textsecuregcm.push.PushServiceClient;
|
||||||
import org.whispersystems.textsecuregcm.push.WebsocketSender;
|
import org.whispersystems.textsecuregcm.push.WebsocketSender;
|
||||||
import org.whispersystems.textsecuregcm.sms.NexmoSmsSender;
|
import org.whispersystems.textsecuregcm.sms.NexmoSmsSender;
|
||||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||||
@@ -89,6 +91,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
import io.dropwizard.Application;
|
import io.dropwizard.Application;
|
||||||
|
import io.dropwizard.client.JerseyClientBuilder;
|
||||||
import io.dropwizard.db.DataSourceFactory;
|
import io.dropwizard.db.DataSourceFactory;
|
||||||
import io.dropwizard.jdbi.DBIFactory;
|
import io.dropwizard.jdbi.DBIFactory;
|
||||||
import io.dropwizard.metrics.graphite.GraphiteReporterFactory;
|
import io.dropwizard.metrics.graphite.GraphiteReporterFactory;
|
||||||
@@ -135,40 +138,34 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||||||
PendingDevices pendingDevices = jdbi.onDemand(PendingDevices.class);
|
PendingDevices pendingDevices = jdbi.onDemand(PendingDevices.class);
|
||||||
Keys keys = jdbi.onDemand(Keys.class);
|
Keys keys = jdbi.onDemand(Keys.class);
|
||||||
|
|
||||||
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
||||||
JedisPool redisClient = new RedisClientFactory(config.getRedisConfiguration()).getRedisClientPool();
|
JedisPool directoryClient = new RedisClientFactory(config.getDirectoryConfiguration().getUrl()).getRedisClientPool();
|
||||||
|
JedisPool messageStoreClient = new RedisClientFactory(config.getMessageStoreConfiguration().getUrl()).getRedisClientPool();
|
||||||
|
Client httpClient = new JerseyClientBuilder(environment).using(config.getJerseyClientConfiguration())
|
||||||
|
.build(getName());
|
||||||
|
|
||||||
DirectoryManager directory = new DirectoryManager(redisClient);
|
DirectoryManager directory = new DirectoryManager(directoryClient);
|
||||||
PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, memcachedClient);
|
PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, memcachedClient);
|
||||||
PendingDevicesManager pendingDevicesManager = new PendingDevicesManager (pendingDevices, memcachedClient );
|
PendingDevicesManager pendingDevicesManager = new PendingDevicesManager (pendingDevices, memcachedClient );
|
||||||
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
||||||
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
||||||
StoredMessages storedMessages = new StoredMessages(redisClient);
|
StoredMessages storedMessages = new StoredMessages(messageStoreClient);
|
||||||
PubSubManager pubSubManager = new PubSubManager(redisClient);
|
PubSubManager pubSubManager = new PubSubManager(messageStoreClient);
|
||||||
|
PushServiceClient pushServiceClient = new PushServiceClient(httpClient, config.getPushConfiguration());
|
||||||
APNSender apnSender = new APNSender(accountsManager, pubSubManager, storedMessages, memcachedClient,
|
WebsocketSender websocketSender = new WebsocketSender(storedMessages, pubSubManager);
|
||||||
config.getApnConfiguration().getCertificate(),
|
AccountAuthenticator deviceAuthenticator = new AccountAuthenticator(accountsManager);
|
||||||
config.getApnConfiguration().getKey());
|
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), memcachedClient);
|
||||||
|
|
||||||
GCMSender gcmSender = new GCMSender(accountsManager,
|
|
||||||
config.getGcmConfiguration().getSenderId(),
|
|
||||||
config.getGcmConfiguration().getApiKey());
|
|
||||||
|
|
||||||
WebsocketSender websocketSender = new WebsocketSender(storedMessages, pubSubManager);
|
|
||||||
|
|
||||||
environment.lifecycle().manage(apnSender);
|
|
||||||
environment.lifecycle().manage(gcmSender);
|
|
||||||
|
|
||||||
AccountAuthenticator deviceAuthenticator = new AccountAuthenticator(accountsManager);
|
|
||||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), memcachedClient);
|
|
||||||
|
|
||||||
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
|
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
|
||||||
Optional<NexmoSmsSender> nexmoSmsSender = initializeNexmoSmsSender(config.getNexmoConfiguration());
|
Optional<NexmoSmsSender> nexmoSmsSender = initializeNexmoSmsSender(config.getNexmoConfiguration());
|
||||||
SmsSender smsSender = new SmsSender(twilioSmsSender, nexmoSmsSender, config.getTwilioConfiguration().isInternational());
|
SmsSender smsSender = new SmsSender(twilioSmsSender, nexmoSmsSender, config.getTwilioConfiguration().isInternational());
|
||||||
UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
|
UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
|
||||||
PushSender pushSender = new PushSender(gcmSender, apnSender, websocketSender);
|
PushSender pushSender = new PushSender(pushServiceClient, websocketSender);
|
||||||
|
FeedbackHandler feedbackHandler = new FeedbackHandler(pushServiceClient, accountsManager);
|
||||||
Optional<byte[]> authorizationKey = config.getRedphoneConfiguration().getAuthorizationKey();
|
Optional<byte[]> authorizationKey = config.getRedphoneConfiguration().getAuthorizationKey();
|
||||||
|
|
||||||
|
environment.lifecycle().manage(feedbackHandler);
|
||||||
|
|
||||||
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
|
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
|
||||||
KeysControllerV1 keysControllerV1 = new KeysControllerV1(rateLimiters, keys, accountsManager, federatedClientManager);
|
KeysControllerV1 keysControllerV1 = new KeysControllerV1(rateLimiters, keys, accountsManager, federatedClientManager);
|
||||||
KeysControllerV2 keysControllerV2 = new KeysControllerV2(rateLimiters, keys, accountsManager, federatedClientManager);
|
KeysControllerV2 keysControllerV2 = new KeysControllerV2(rateLimiters, keys, accountsManager, federatedClientManager);
|
||||||
@@ -191,15 +188,17 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||||||
environment.jersey().register(messageController);
|
environment.jersey().register(messageController);
|
||||||
|
|
||||||
if (config.getWebsocketConfiguration().isEnabled()) {
|
if (config.getWebsocketConfiguration().isEnabled()) {
|
||||||
WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment(environment);
|
WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment(environment, config);
|
||||||
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(deviceAuthenticator));
|
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(deviceAuthenticator));
|
||||||
webSocketEnvironment.setConnectListener(new ConnectListener(accountsManager, pushSender, storedMessages, pubSubManager));
|
webSocketEnvironment.setConnectListener(new ConnectListener(accountsManager, pushSender, storedMessages, pubSubManager));
|
||||||
|
webSocketEnvironment.jersey().register(new KeepAliveController());
|
||||||
|
|
||||||
WebSocketResourceProviderFactory servlet = new WebSocketResourceProviderFactory(webSocketEnvironment);
|
WebSocketResourceProviderFactory servlet = new WebSocketResourceProviderFactory(webSocketEnvironment);
|
||||||
|
|
||||||
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", servlet);
|
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", servlet);
|
||||||
websocket.addMapping("/v1/websocket/*");
|
websocket.addMapping("/v1/websocket/*");
|
||||||
websocket.setAsyncSupported(true);
|
websocket.setAsyncSupported(true);
|
||||||
|
servlet.start();
|
||||||
|
|
||||||
FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
|
FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
|
||||||
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
|
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
|
||||||
@@ -210,7 +209,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||||||
filter.setInitParameter("allowCredentials", "true");
|
filter.setInitParameter("allowCredentials", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
environment.healthChecks().register("redis", new RedisHealthCheck(redisClient));
|
environment.healthChecks().register("directory", new RedisHealthCheck(directoryClient));
|
||||||
|
environment.healthChecks().register("messagestore", new RedisHealthCheck(messageStoreClient));
|
||||||
environment.healthChecks().register("memcache", new MemcacheHealthCheck(memcachedClient));
|
environment.healthChecks().register("memcache", new MemcacheHealthCheck(memcachedClient));
|
||||||
|
|
||||||
environment.jersey().register(new IOExceptionMapper());
|
environment.jersey().register(new IOExceptionMapper());
|
||||||
@@ -229,13 +229,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||||||
GraphiteReporter graphiteReporter = (GraphiteReporter) graphiteReporterFactory.build(environment.metrics());
|
GraphiteReporter graphiteReporter = (GraphiteReporter) graphiteReporterFactory.build(environment.metrics());
|
||||||
graphiteReporter.start(15, TimeUnit.SECONDS);
|
graphiteReporter.start(15, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getMetricsConfiguration().isEnabled()) {
|
|
||||||
new JsonMetricsReporter(environment.metrics(),
|
|
||||||
config.getMetricsConfiguration().getToken(),
|
|
||||||
config.getMetricsConfiguration().getHost())
|
|
||||||
.start(60, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<NexmoSmsSender> initializeNexmoSmsSender(NexmoConfiguration configuration) {
|
private Optional<NexmoSmsSender> initializeNexmoSmsSender(NexmoConfiguration configuration) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||||||
import org.hibernate.validator.constraints.NotEmpty;
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
import org.hibernate.validator.constraints.URL;
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
public class RedisConfiguration {
|
public class DirectoryConfiguration {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
@@ -18,12 +18,8 @@ package org.whispersystems.textsecuregcm.configuration;
|
|||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FederationConfiguration {
|
public class FederationConfiguration {
|
||||||
@@ -34,31 +30,7 @@ public class FederationConfiguration {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String herokuPeers;
|
|
||||||
|
|
||||||
public List<FederatedPeer> getPeers() {
|
public List<FederatedPeer> getPeers() {
|
||||||
if (peers != null) {
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (herokuPeers != null) {
|
|
||||||
List<FederatedPeer> peers = new LinkedList<>();
|
|
||||||
JsonElement root = new JsonParser().parse(herokuPeers);
|
|
||||||
JsonArray peerElements = root.getAsJsonArray();
|
|
||||||
|
|
||||||
for (JsonElement peer : peerElements) {
|
|
||||||
String name = peer.getAsJsonObject().get("name").getAsString();
|
|
||||||
String url = peer.getAsJsonObject().get("url").getAsString();
|
|
||||||
String authenticationToken = peer.getAsJsonObject().get("authenticationToken").getAsString();
|
|
||||||
String certificate = peer.getAsJsonObject().get("certificate").getAsString();
|
|
||||||
|
|
||||||
peers.add(new FederatedPeer(name, url, authenticationToken, certificate));
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.configuration;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
public class MessageStoreConfiguration {
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package org.whispersystems.textsecuregcm.configuration;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
public class MetricsConfiguration {
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String host;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private boolean enabled = false;
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled && token != null && host != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.configuration;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
|
public class PushConfiguration {
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Min(1)
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.controllers;
|
||||||
|
|
||||||
|
import com.codahale.metrics.annotation.Timed;
|
||||||
|
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
|
||||||
|
@Path("/v1/keepalive")
|
||||||
|
public class KeepAliveController {
|
||||||
|
|
||||||
|
@Timed
|
||||||
|
@GET
|
||||||
|
public Response getKeepAlive() {
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
|
public class ApnMessage {
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String apnId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String number;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Min(1)
|
||||||
|
private int deviceId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ApnMessage() {}
|
||||||
|
|
||||||
|
public ApnMessage(String apnId, String number, int deviceId, String message) {
|
||||||
|
this.apnId = apnId;
|
||||||
|
this.number = number;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,15 +18,10 @@ package org.whispersystems.textsecuregcm.entities;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||||
@@ -73,9 +68,9 @@ public class ClientContact {
|
|||||||
this.inactive = inactive;
|
this.inactive = inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
// public String toString() {
|
||||||
return new Gson().toJson(this);
|
// return new Gson().toJson(this);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ public class EncryptedOutgoingMessage {
|
|||||||
private static final int MAC_KEY_SIZE = 20;
|
private static final int MAC_KEY_SIZE = 20;
|
||||||
private static final int MAC_SIZE = 10;
|
private static final int MAC_SIZE = 10;
|
||||||
|
|
||||||
private final String serialized;
|
private final byte[] serialized;
|
||||||
|
private final String serializedAndEncoded;
|
||||||
|
|
||||||
public EncryptedOutgoingMessage(OutgoingMessageSignal outgoingMessage,
|
public EncryptedOutgoingMessage(OutgoingMessageSignal outgoingMessage,
|
||||||
String signalingKey)
|
String signalingKey)
|
||||||
@@ -50,12 +51,16 @@ public class EncryptedOutgoingMessage {
|
|||||||
byte[] plaintext = outgoingMessage.toByteArray();
|
byte[] plaintext = outgoingMessage.toByteArray();
|
||||||
SecretKeySpec cipherKey = getCipherKey (signalingKey);
|
SecretKeySpec cipherKey = getCipherKey (signalingKey);
|
||||||
SecretKeySpec macKey = getMacKey(signalingKey);
|
SecretKeySpec macKey = getMacKey(signalingKey);
|
||||||
byte[] ciphertext = getCiphertext(plaintext, cipherKey, macKey);
|
|
||||||
|
|
||||||
this.serialized = Base64.encodeBytes(ciphertext);
|
this.serialized = getCiphertext(plaintext, cipherKey, macKey);
|
||||||
|
this.serializedAndEncoded = Base64.encodeBytes(this.serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String serialize() {
|
public String toEncodedString() {
|
||||||
|
return serializedAndEncoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toByteArray() {
|
||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
|
public class GcmMessage {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String gcmId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String number;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Min(1)
|
||||||
|
private int deviceId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private boolean receipt;
|
||||||
|
|
||||||
|
public GcmMessage() {}
|
||||||
|
|
||||||
|
public GcmMessage(String gcmId, String number, int deviceId, String message, boolean receipt) {
|
||||||
|
this.gcmId = gcmId;
|
||||||
|
this.number = number;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.message = message;
|
||||||
|
this.receipt = receipt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package org.whispersystems.textsecuregcm.entities;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
public class PendingMessage {
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String sender;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private long messageId;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String encryptedOutgoingMessage;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private boolean receipt;
|
|
||||||
|
|
||||||
public PendingMessage() {}
|
|
||||||
|
|
||||||
public PendingMessage(String sender, long messageId, boolean receipt, String encryptedOutgoingMessage) {
|
|
||||||
this.sender = sender;
|
|
||||||
this.messageId = messageId;
|
|
||||||
this.receipt = receipt;
|
|
||||||
this.encryptedOutgoingMessage = encryptedOutgoingMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEncryptedOutgoingMessage() {
|
|
||||||
return encryptedOutgoingMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMessageId() {
|
|
||||||
return messageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSender() {
|
|
||||||
return sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReceipt() {
|
|
||||||
return receipt;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
if (other == null || !(other instanceof PendingMessage)) return false;
|
|
||||||
PendingMessage that = (PendingMessage)other;
|
|
||||||
|
|
||||||
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() ^ (receipt ? 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
|
public class UnregisteredEvent {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String registrationId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotEmpty
|
||||||
|
private String number;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Min(1)
|
||||||
|
private int deviceId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
|
public String getRegistrationId() {
|
||||||
|
return registrationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeviceId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UnregisteredEventList {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private List<UnregisteredEvent> devices;
|
||||||
|
|
||||||
|
public List<UnregisteredEvent> getDevices() {
|
||||||
|
if (devices == null) return new LinkedList<>();
|
||||||
|
else return devices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,9 @@ public class IOExceptionMapper implements ExceptionMapper<IOException> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response toResponse(IOException e) {
|
public Response toResponse(IOException e) {
|
||||||
logger.warn("IOExceptionMapper", e);
|
if (!(e.getCause() instanceof java.util.concurrent.TimeoutException)) {
|
||||||
|
logger.warn("IOExceptionMapper", e);
|
||||||
|
}
|
||||||
return Response.status(503).build();
|
return Response.status(503).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,25 +27,25 @@ import java.util.SortedMap;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapted from MetricsServlet.
|
|
||||||
*/
|
|
||||||
public class JsonMetricsReporter extends ScheduledReporter {
|
public class JsonMetricsReporter extends ScheduledReporter {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(JsonMetricsReporter.class);
|
private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]");
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(JsonMetricsReporter.class);
|
||||||
private final JsonFactory factory = new JsonFactory();
|
private final JsonFactory factory = new JsonFactory();
|
||||||
|
|
||||||
private final String table;
|
private final String token;
|
||||||
private final String sunnylabsHost;
|
private final String hostname;
|
||||||
private final String host;
|
private final String host;
|
||||||
|
|
||||||
public JsonMetricsReporter(MetricRegistry registry, String token, String sunnylabsHost)
|
public JsonMetricsReporter(MetricRegistry registry, String token, String hostname,
|
||||||
|
MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit)
|
||||||
throws UnknownHostException
|
throws UnknownHostException
|
||||||
{
|
{
|
||||||
super(registry, "jsonmetrics-reporter", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS);
|
super(registry, "json-reporter", filter, rateUnit, durationUnit);
|
||||||
this.table = token;
|
this.token = token;
|
||||||
this.sunnylabsHost = sunnylabsHost;
|
this.hostname = hostname;
|
||||||
this.host = InetAddress.getLocalHost().getHostName();
|
this.host = InetAddress.getLocalHost().getHostName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,8 +56,8 @@ public class JsonMetricsReporter extends ScheduledReporter {
|
|||||||
SortedMap<String, Timer> stringTimerSortedMap)
|
SortedMap<String, Timer> stringTimerSortedMap)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
logger.info("Reporting metrics...");
|
logger.debug("Reporting metrics...");
|
||||||
URL url = new URL("https", sunnylabsHost, 443, "/report/metrics?t=" + table + "&h=" + host);
|
URL url = new URL("https", hostname, 443, String.format("/report/metrics?t=%s&h=%s", token, host));
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
@@ -93,7 +93,7 @@ public class JsonMetricsReporter extends ScheduledReporter {
|
|||||||
|
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
|
|
||||||
logger.info("Metrics server response: " + connection.getResponseCode());
|
logger.debug("Metrics server response: " + connection.getResponseCode());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Error sending metrics", e);
|
logger.warn("Error sending metrics", e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -175,10 +175,66 @@ public class JsonMetricsReporter extends ScheduledReporter {
|
|||||||
json.writeNumberField("m15", convertRate(meter.getFifteenMinuteRate()));
|
json.writeNumberField("m15", convertRate(meter.getFifteenMinuteRate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]");
|
|
||||||
|
|
||||||
private String sanitize(String metricName) {
|
private String sanitize(String metricName) {
|
||||||
return SIMPLE_NAMES.matcher(metricName).replaceAll("_");
|
return SIMPLE_NAMES.matcher(metricName).replaceAll("_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Builder forRegistry(MetricRegistry registry) {
|
||||||
|
return new Builder(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private final MetricRegistry registry;
|
||||||
|
private MetricFilter filter = MetricFilter.ALL;
|
||||||
|
private TimeUnit rateUnit = TimeUnit.SECONDS;
|
||||||
|
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
|
||||||
|
private String token;
|
||||||
|
private String hostname;
|
||||||
|
|
||||||
|
private Builder(MetricRegistry registry) {
|
||||||
|
this.registry = registry;
|
||||||
|
this.rateUnit = TimeUnit.SECONDS;
|
||||||
|
this.durationUnit = TimeUnit.MILLISECONDS;
|
||||||
|
this.filter = MetricFilter.ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder convertRatesTo(TimeUnit rateUnit) {
|
||||||
|
this.rateUnit = rateUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder convertDurationsTo(TimeUnit durationUnit) {
|
||||||
|
this.durationUnit = durationUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder filter(MetricFilter filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withHostname(String hostname) {
|
||||||
|
this.hostname = hostname;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonMetricsReporter build() throws UnknownHostException {
|
||||||
|
if (hostname == null) {
|
||||||
|
throw new IllegalArgumentException("No hostname specified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw new IllegalArgumentException("No token specified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonMetricsReporter(registry, token, hostname, filter, rateUnit, durationUnit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
import com.codahale.metrics.ScheduledReporter;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import io.dropwizard.metrics.BaseReporterFactory;
|
||||||
|
|
||||||
|
@JsonTypeName("json")
|
||||||
|
public class JsonMetricsReporterFactory extends BaseReporterFactory {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotNull
|
||||||
|
private String hostname;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@NotNull
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledReporter build(MetricRegistry metricRegistry) {
|
||||||
|
try {
|
||||||
|
return JsonMetricsReporter.forRegistry(metricRegistry)
|
||||||
|
.withHostname(hostname)
|
||||||
|
.withToken(token)
|
||||||
|
.convertRatesTo(getRateUnit())
|
||||||
|
.convertDurationsTo(getDurationUnit())
|
||||||
|
.filter(getFilter())
|
||||||
|
.build();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.providers;
|
package org.whispersystems.textsecuregcm.providers;
|
||||||
|
|
||||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -30,11 +30,11 @@ public class RedisClientFactory {
|
|||||||
|
|
||||||
private final JedisPool jedisPool;
|
private final JedisPool jedisPool;
|
||||||
|
|
||||||
public RedisClientFactory(RedisConfiguration redisConfig) throws URISyntaxException {
|
public RedisClientFactory(String url) throws URISyntaxException {
|
||||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||||
poolConfig.setTestOnBorrow(true);
|
poolConfig.setTestOnBorrow(true);
|
||||||
|
|
||||||
URI redisURI = new URI(redisConfig.getUrl());
|
URI redisURI = new URI(url);
|
||||||
String redisHost = redisURI.getHost();
|
String redisHost = redisURI.getHost();
|
||||||
int redisPort = redisURI.getPort();
|
int redisPort = redisURI.getPort();
|
||||||
String redisPassword = null;
|
String redisPassword = null;
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
|
||||||
import io.dropwizard.lifecycle.Managed;
|
|
||||||
|
|
||||||
public class APNSender implements Managed {
|
|
||||||
|
|
||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
|
||||||
private final Meter websocketMeter = metricRegistry.meter(name(getClass(), "websocket"));
|
|
||||||
private final Meter pushMeter = metricRegistry.meter(name(getClass(), "push"));
|
|
||||||
private final Meter failureMeter = metricRegistry.meter(name(getClass(), "failure"));
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(APNSender.class);
|
|
||||||
|
|
||||||
private static final String MESSAGE_BODY = "m";
|
|
||||||
|
|
||||||
private static final ObjectMapper mapper = SystemMapper.getMapper();
|
|
||||||
|
|
||||||
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
|
|
||||||
private final AccountsManager accounts;
|
|
||||||
private final PubSubManager pubSubManager;
|
|
||||||
private final StoredMessages storedMessages;
|
|
||||||
private final MemcachedClient memcachedClient;
|
|
||||||
|
|
||||||
private final String apnCertificate;
|
|
||||||
private final String apnKey;
|
|
||||||
|
|
||||||
private Optional<ApnsService> apnService;
|
|
||||||
|
|
||||||
public APNSender(AccountsManager accounts,
|
|
||||||
PubSubManager pubSubManager,
|
|
||||||
StoredMessages storedMessages,
|
|
||||||
MemcachedClient memcachedClient,
|
|
||||||
String apnCertificate, String apnKey)
|
|
||||||
{
|
|
||||||
this.accounts = accounts;
|
|
||||||
this.pubSubManager = pubSubManager;
|
|
||||||
this.storedMessages = storedMessages;
|
|
||||||
this.apnCertificate = apnCertificate;
|
|
||||||
this.apnKey = apnKey;
|
|
||||||
this.memcachedClient = memcachedClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(Account account, Device device,
|
|
||||||
String registrationId, PendingMessage message)
|
|
||||||
throws TransientPushFailureException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
String serializedPendingMessage = mapper.writeValueAsString(message);
|
|
||||||
WebsocketAddress websocketAddress = new WebsocketAddress(account.getNumber(), device.getId());
|
|
||||||
|
|
||||||
if (pubSubManager.publish(websocketAddress, new PubSubMessage(PubSubMessage.TYPE_DELIVER,
|
|
||||||
serializedPendingMessage)))
|
|
||||||
{
|
|
||||||
websocketMeter.mark();
|
|
||||||
} else {
|
|
||||||
memcacheSet(registrationId, account.getNumber());
|
|
||||||
storedMessages.insert(websocketAddress, message);
|
|
||||||
|
|
||||||
if (!message.isReceipt()) {
|
|
||||||
sendPush(registrationId, serializedPendingMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new TransientPushFailureException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPush(String registrationId, String message)
|
|
||||||
throws TransientPushFailureException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (!apnService.isPresent()) {
|
|
||||||
failureMeter.mark();
|
|
||||||
throw new TransientPushFailureException("APN access not configured!");
|
|
||||||
}
|
|
||||||
|
|
||||||
String payload = APNS.newPayload()
|
|
||||||
.alertBody("Message!")
|
|
||||||
.customField(MESSAGE_BODY, message)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
logger.debug("APN Payload: " + payload);
|
|
||||||
|
|
||||||
apnService.get().push(registrationId, payload);
|
|
||||||
pushMeter.mark();
|
|
||||||
} catch (NetworkIOException nioe) {
|
|
||||||
logger.warn("Network Error", nioe);
|
|
||||||
failureMeter.mark();
|
|
||||||
throw new TransientPushFailureException(nioe);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] initializeKeyStore(String pemCertificate, String pemKey)
|
|
||||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException
|
|
||||||
{
|
|
||||||
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemCertificate.getBytes())));
|
|
||||||
X509Certificate certificate = (X509Certificate) reader.readObject();
|
|
||||||
Certificate[] certificateChain = {certificate};
|
|
||||||
|
|
||||||
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemKey.getBytes())));
|
|
||||||
KeyPair keyPair = (KeyPair) reader.readObject();
|
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("pkcs12");
|
|
||||||
keyStore.load(null);
|
|
||||||
keyStore.setEntry("apn",
|
|
||||||
new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), certificateChain),
|
|
||||||
new KeyStore.PasswordProtection("insecure".toCharArray()));
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
keyStore.store(baos, "insecure".toCharArray());
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws Exception {
|
|
||||||
if (!Util.isEmpty(apnCertificate) && !Util.isEmpty(apnKey)) {
|
|
||||||
byte[] keyStore = initializeKeyStore(apnCertificate, apnKey);
|
|
||||||
|
|
||||||
this.apnService = Optional.of(APNS.newService()
|
|
||||||
.withCert(new ByteArrayInputStream(keyStore), "insecure")
|
|
||||||
.asQueued()
|
|
||||||
.withSandboxDestination().build());
|
|
||||||
|
|
||||||
this.executor.scheduleAtFixedRate(new FeedbackRunnable(), 0, 1, TimeUnit.HOURS);
|
|
||||||
} else {
|
|
||||||
this.apnService = Optional.absent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() throws Exception {
|
|
||||||
if (apnService.isPresent()) {
|
|
||||||
apnService.get().stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void memcacheSet(String registrationId, String number) {
|
|
||||||
if (memcachedClient != null) {
|
|
||||||
memcachedClient.set("APN-" + registrationId, 60 * 60 * 24, number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> memcacheGet(String registrationId) {
|
|
||||||
if (memcachedClient != null) {
|
|
||||||
return Optional.fromNullable((String)memcachedClient.get("APN-" + registrationId));
|
|
||||||
} else {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FeedbackRunnable implements Runnable {
|
|
||||||
private void updateAccount(Account account, String registrationId) {
|
|
||||||
boolean needsUpdate = false;
|
|
||||||
|
|
||||||
for (Device device : account.getDevices()) {
|
|
||||||
if (registrationId.equals(device.getApnId())) {
|
|
||||||
needsUpdate = true;
|
|
||||||
device.setApnId(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsUpdate) {
|
|
||||||
accounts.update(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (apnService.isPresent()) {
|
|
||||||
Map<String, Date> inactiveDevices = apnService.get().getInactiveDevices();
|
|
||||||
|
|
||||||
for (String registrationId : inactiveDevices.keySet()) {
|
|
||||||
Optional<String> number = memcacheGet(registrationId);
|
|
||||||
|
|
||||||
if (number.isPresent()) {
|
|
||||||
Optional<Account> account = accounts.get(number.get());
|
|
||||||
|
|
||||||
if (account.isPresent()) {
|
|
||||||
updateAccount(account.get(), registrationId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn("APN unregister event received for uncached ID: " + registrationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.push;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.UnregisteredEvent;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import io.dropwizard.lifecycle.Managed;
|
||||||
|
|
||||||
|
public class FeedbackHandler implements Managed, Runnable {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(PushServiceClient.class);
|
||||||
|
|
||||||
|
private final PushServiceClient client;
|
||||||
|
private final AccountsManager accountsManager;
|
||||||
|
|
||||||
|
private ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
public FeedbackHandler(PushServiceClient client, AccountsManager accountsManager) {
|
||||||
|
this.client = client;
|
||||||
|
this.accountsManager = accountsManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws Exception {
|
||||||
|
this.executor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
this.executor.scheduleAtFixedRate(this, 0, 1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() throws Exception {
|
||||||
|
if (this.executor != null) {
|
||||||
|
this.executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
List<UnregisteredEvent> gcmFeedback = client.getGcmFeedback();
|
||||||
|
List<UnregisteredEvent> apnFeedback = client.getApnFeedback();
|
||||||
|
|
||||||
|
for (UnregisteredEvent gcmEvent : gcmFeedback) {
|
||||||
|
handleGcmUnregistered(gcmEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UnregisteredEvent apnEvent : apnFeedback) {
|
||||||
|
handleApnUnregistered(apnEvent);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Error retrieving feedback: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGcmUnregistered(UnregisteredEvent event) {
|
||||||
|
logger.info("Got GCM Unregistered: " + event.getNumber() + "," + event.getDeviceId());
|
||||||
|
|
||||||
|
Optional<Account> account = accountsManager.get(event.getNumber());
|
||||||
|
|
||||||
|
if (account.isPresent()) {
|
||||||
|
Optional<Device> device = account.get().getDevice(event.getDeviceId());
|
||||||
|
|
||||||
|
if (device.isPresent()) {
|
||||||
|
if (event.getRegistrationId().equals(device.get().getGcmId())) {
|
||||||
|
logger.info("GCM Unregister GCM ID matches!");
|
||||||
|
if (device.get().getPushTimestamp() == 0 ||
|
||||||
|
event.getTimestamp() > device.get().getPushTimestamp())
|
||||||
|
{
|
||||||
|
logger.info("GCM Unregister Timestamp matches!");
|
||||||
|
device.get().setGcmId(null);
|
||||||
|
accountsManager.update(account.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleApnUnregistered(UnregisteredEvent event) {
|
||||||
|
logger.info("Got APN Unregistered: " + event.getNumber() + "," + event.getDeviceId());
|
||||||
|
|
||||||
|
Optional<Account> account = accountsManager.get(event.getNumber());
|
||||||
|
|
||||||
|
if (account.isPresent()) {
|
||||||
|
Optional<Device> device = account.get().getDevice(event.getDeviceId());
|
||||||
|
|
||||||
|
if (device.isPresent()) {
|
||||||
|
if (event.getRegistrationId().equals(device.get().getApnId())) {
|
||||||
|
logger.info("APN Unregister APN ID matches!");
|
||||||
|
if (device.get().getPushTimestamp() == 0 ||
|
||||||
|
event.getTimestamp() > device.get().getPushTimestamp())
|
||||||
|
{
|
||||||
|
logger.info("APN Unregister timestamp matches!");
|
||||||
|
device.get().setApnId(null);
|
||||||
|
accountsManager.update(account.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
package org.whispersystems.textsecuregcm.push;
|
|
||||||
|
|
||||||
import com.codahale.metrics.Meter;
|
|
||||||
import com.codahale.metrics.MetricRegistry;
|
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
|
||||||
import com.google.common.base.Optional;
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
|
||||||
import org.jivesoftware.smack.ConnectionListener;
|
|
||||||
import org.jivesoftware.smack.PacketListener;
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
|
||||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
|
||||||
import org.jivesoftware.smack.packet.DefaultPacketExtension;
|
|
||||||
import org.jivesoftware.smack.packet.Message;
|
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
|
||||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
|
||||||
import org.jivesoftware.smack.provider.ProviderManager;
|
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.JSONValue;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
|
||||||
import io.dropwizard.lifecycle.Managed;
|
|
||||||
|
|
||||||
public class GCMSender implements Managed, PacketListener {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(GCMSender.class);
|
|
||||||
|
|
||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(org.whispersystems.textsecuregcm.util.Constants.METRICS_NAME);
|
|
||||||
private final Meter success = metricRegistry.meter(name(getClass(), "sent", "success"));
|
|
||||||
private final Meter failure = metricRegistry.meter(name(getClass(), "sent", "failure"));
|
|
||||||
private final Meter unregistered = metricRegistry.meter(name(getClass(), "sent", "unregistered"));
|
|
||||||
|
|
||||||
private static final String GCM_SERVER = "gcm.googleapis.com";
|
|
||||||
private static final int GCM_PORT = 5235;
|
|
||||||
|
|
||||||
private static final String GCM_ELEMENT_NAME = "gcm";
|
|
||||||
private static final String GCM_NAMESPACE = "google:mobile:data";
|
|
||||||
|
|
||||||
private final Map<String, UnacknowledgedMessage> pendingMessages = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final long senderId;
|
|
||||||
private final String apiKey;
|
|
||||||
private final AccountsManager accounts;
|
|
||||||
|
|
||||||
private XMPPTCPConnection connection;
|
|
||||||
|
|
||||||
public GCMSender(AccountsManager accounts, long senderId, String apiKey) {
|
|
||||||
this.accounts = accounts;
|
|
||||||
this.senderId = senderId;
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
|
|
||||||
ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
|
|
||||||
new GcmPacketExtensionProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(String destinationNumber, long destinationDeviceId,
|
|
||||||
String registrationId, PendingMessage message)
|
|
||||||
{
|
|
||||||
String messageId = "m-" + UUID.randomUUID().toString();
|
|
||||||
UnacknowledgedMessage unacknowledgedMessage = new UnacknowledgedMessage(destinationNumber,
|
|
||||||
destinationDeviceId,
|
|
||||||
registrationId, message);
|
|
||||||
|
|
||||||
sendMessage(messageId, unacknowledgedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(String messageId, UnacknowledgedMessage message) {
|
|
||||||
try {
|
|
||||||
boolean isReceipt = message.getPendingMessage().isReceipt();
|
|
||||||
|
|
||||||
Map<String, String> dataObject = new HashMap<>();
|
|
||||||
dataObject.put("type", "message");
|
|
||||||
dataObject.put(isReceipt ? "receipt" : "message", message.getPendingMessage().getEncryptedOutgoingMessage());
|
|
||||||
|
|
||||||
Map<String, Object> messageObject = new HashMap<>();
|
|
||||||
messageObject.put("to", message.getRegistrationId());
|
|
||||||
messageObject.put("message_id", messageId);
|
|
||||||
messageObject.put("data", dataObject);
|
|
||||||
|
|
||||||
String json = JSONObject.toJSONString(messageObject);
|
|
||||||
|
|
||||||
pendingMessages.put(messageId, message);
|
|
||||||
connection.sendPacket(new GcmPacketExtension(json).toPacket());
|
|
||||||
} catch (SmackException.NotConnectedException e) {
|
|
||||||
logger.warn("GCMClient", "No connection", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws Exception {
|
|
||||||
this.connection = connect(senderId, apiKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() throws Exception {
|
|
||||||
this.connection.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void processPacket(Packet packet) throws SmackException.NotConnectedException {
|
|
||||||
Message incomingMessage = (Message) packet;
|
|
||||||
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
|
|
||||||
String json = gcmPacket.getJson();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue.parseWithException(json);
|
|
||||||
Object messageType = jsonObject.get("message_type");
|
|
||||||
|
|
||||||
if (messageType == null) {
|
|
||||||
handleUpstreamMessage(jsonObject);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (messageType.toString()) {
|
|
||||||
case "ack" : handleAckReceipt(jsonObject); break;
|
|
||||||
case "nack" : handleNackReceipt(jsonObject); break;
|
|
||||||
case "receipt" : handleDeliveryReceipt(jsonObject); break;
|
|
||||||
case "control" : handleControlMessage(jsonObject); break;
|
|
||||||
default:
|
|
||||||
logger.warn("Received unknown GCM message: " + messageType.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ParseException e) {
|
|
||||||
logger.warn("GCMClient", "Received unparsable message", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("GCMClient", "Failed to process packet", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleControlMessage(Map<String, Object> message) {
|
|
||||||
String controlType = (String) message.get("control_type");
|
|
||||||
|
|
||||||
if ("CONNECTION_DRAINING".equals(controlType)) {
|
|
||||||
logger.warn("GCM Connection is draining! Initiating reconnect...");
|
|
||||||
reconnect();
|
|
||||||
} else {
|
|
||||||
logger.warn("Received unknown GCM control message: " + controlType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDeliveryReceipt(Map<String, Object> message) {
|
|
||||||
logger.warn("Got delivery receipt!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNackReceipt(Map<String, Object> message) {
|
|
||||||
String messageId = (String) message.get("message_id");
|
|
||||||
String errorCode = (String) message.get("error");
|
|
||||||
|
|
||||||
if (errorCode == null) {
|
|
||||||
logger.warn("Null GCM error code!");
|
|
||||||
if (messageId != null) {
|
|
||||||
pendingMessages.remove(messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (errorCode) {
|
|
||||||
case "BAD_REGISTRATION" : handleBadRegistration(message); break;
|
|
||||||
case "DEVICE_UNREGISTERED" : handleBadRegistration(message); break;
|
|
||||||
case "INTERNAL_SERVER_ERROR" : handleServerFailure(message); break;
|
|
||||||
case "INVALID_JSON" : handleClientFailure(message); break;
|
|
||||||
case "QUOTA_EXCEEDED" : handleClientFailure(message); break;
|
|
||||||
case "SERVICE_UNAVAILABLE" : handleServerFailure(message); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleAckReceipt(Map<String, Object> message) {
|
|
||||||
success.mark();
|
|
||||||
|
|
||||||
String messageId = (String) message.get("message_id");
|
|
||||||
|
|
||||||
if (messageId != null) {
|
|
||||||
pendingMessages.remove(messageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUpstreamMessage(Map<String, Object> message)
|
|
||||||
throws SmackException.NotConnectedException
|
|
||||||
{
|
|
||||||
logger.warn("Got upstream message from GCM Server!");
|
|
||||||
|
|
||||||
for (String key : message.keySet()) {
|
|
||||||
logger.warn(key + " : " + message.get(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> ack = new HashMap<>();
|
|
||||||
message.put("message_type", "ack");
|
|
||||||
message.put("to", message.get("from"));
|
|
||||||
message.put("message_id", message.get("message_id"));
|
|
||||||
|
|
||||||
String json = JSONValue.toJSONString(ack);
|
|
||||||
|
|
||||||
Packet request = new GcmPacketExtension(json).toPacket();
|
|
||||||
connection.sendPacket(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleBadRegistration(Map<String, Object> message) {
|
|
||||||
unregistered.mark();
|
|
||||||
|
|
||||||
String messageId = (String) message.get("message_id");
|
|
||||||
|
|
||||||
if (messageId != null) {
|
|
||||||
UnacknowledgedMessage unacknowledgedMessage = pendingMessages.remove(messageId);
|
|
||||||
|
|
||||||
if (unacknowledgedMessage != null) {
|
|
||||||
Optional<Account> account = accounts.get(unacknowledgedMessage.getDestinationNumber());
|
|
||||||
|
|
||||||
if (account.isPresent()) {
|
|
||||||
Optional<Device> device = account.get().getDevice(unacknowledgedMessage.getDestinationDeviceId());
|
|
||||||
|
|
||||||
if (device.isPresent()) {
|
|
||||||
device.get().setGcmId(null);
|
|
||||||
accounts.update(account.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleServerFailure(Map<String, Object> message) {
|
|
||||||
failure.mark();
|
|
||||||
|
|
||||||
String messageId = (String)message.get("message_id");
|
|
||||||
|
|
||||||
if (messageId != null) {
|
|
||||||
UnacknowledgedMessage unacknowledgedMessage = pendingMessages.remove(messageId);
|
|
||||||
|
|
||||||
if (unacknowledgedMessage != null) {
|
|
||||||
sendMessage(messageId, unacknowledgedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleClientFailure(Map<String, Object> message) {
|
|
||||||
failure.mark();
|
|
||||||
|
|
||||||
logger.warn("Unrecoverable error: " + message.get("error"));
|
|
||||||
String messageId = (String)message.get("message_id");
|
|
||||||
|
|
||||||
if (messageId != null) {
|
|
||||||
pendingMessages.remove(messageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reconnect() {
|
|
||||||
try {
|
|
||||||
this.connection.disconnect();
|
|
||||||
} catch (SmackException.NotConnectedException e) {
|
|
||||||
logger.warn("GCMClient", "Disconnect attempt", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
this.connection = connect(senderId, apiKey);
|
|
||||||
return;
|
|
||||||
} catch (XMPPException | IOException | SmackException e) {
|
|
||||||
logger.warn("GCMClient", "Reconnecting", e);
|
|
||||||
Util.sleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private XMPPTCPConnection connect(long senderId, String apiKey)
|
|
||||||
throws XMPPException, IOException, SmackException
|
|
||||||
{
|
|
||||||
ConnectionConfiguration config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
|
|
||||||
config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);
|
|
||||||
config.setReconnectionAllowed(true);
|
|
||||||
config.setRosterLoadedAtLogin(false);
|
|
||||||
config.setSendPresence(false);
|
|
||||||
config.setSocketFactory(SSLSocketFactory.getDefault());
|
|
||||||
|
|
||||||
XMPPTCPConnection connection = new XMPPTCPConnection(config);
|
|
||||||
connection.connect();
|
|
||||||
|
|
||||||
connection.addConnectionListener(new LoggingConnectionListener());
|
|
||||||
connection.addPacketListener(this, new PacketTypeFilter(Message.class));
|
|
||||||
|
|
||||||
connection.login(senderId + "@gcm.googleapis.com", apiKey);
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GcmPacketExtensionProvider implements PacketExtensionProvider {
|
|
||||||
@Override
|
|
||||||
public PacketExtension parseExtension(XmlPullParser xmlPullParser) throws Exception {
|
|
||||||
String json = xmlPullParser.nextText();
|
|
||||||
return new GcmPacketExtension(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class GcmPacketExtension extends DefaultPacketExtension {
|
|
||||||
|
|
||||||
private final String json;
|
|
||||||
|
|
||||||
public GcmPacketExtension(String json) {
|
|
||||||
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
|
|
||||||
this.json = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJson() {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toXML() {
|
|
||||||
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME, GCM_NAMESPACE,
|
|
||||||
StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Packet toPacket() {
|
|
||||||
Message message = new Message();
|
|
||||||
message.addExtension(this);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LoggingConnectionListener implements ConnectionListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connected(XMPPConnection xmppConnection) {
|
|
||||||
logger.warn("GCM XMPP Connected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void authenticated(XMPPConnection xmppConnection) {
|
|
||||||
logger.warn("GCM XMPP Authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reconnectionSuccessful() {
|
|
||||||
logger.warn("GCM XMPP Reconnecting..");
|
|
||||||
Iterator<Map.Entry<String, UnacknowledgedMessage>> iterator =
|
|
||||||
pendingMessages.entrySet().iterator();
|
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Map.Entry<String, UnacknowledgedMessage> entry = iterator.next();
|
|
||||||
iterator.remove();
|
|
||||||
|
|
||||||
sendMessage(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reconnectionFailed(Exception e) {
|
|
||||||
logger.warn("GCM XMPP Reconnection failed!", e);
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reconnectingIn(int seconds) {
|
|
||||||
logger.warn(String.format("GCM XMPP Reconnecting in %d secs", seconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionClosedOnError(Exception e) {
|
|
||||||
logger.warn("GCM XMPP Connection closed on error.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionClosed() {
|
|
||||||
logger.warn("GCM XMPP Connection closed.");
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UnacknowledgedMessage {
|
|
||||||
private final String destinationNumber;
|
|
||||||
private final long destinationDeviceId;
|
|
||||||
|
|
||||||
private final String registrationId;
|
|
||||||
private final PendingMessage pendingMessage;
|
|
||||||
|
|
||||||
private UnacknowledgedMessage(String destinationNumber,
|
|
||||||
long destinationDeviceId,
|
|
||||||
String registrationId,
|
|
||||||
PendingMessage pendingMessage)
|
|
||||||
{
|
|
||||||
this.destinationNumber = destinationNumber;
|
|
||||||
this.destinationDeviceId = destinationDeviceId;
|
|
||||||
this.registrationId = registrationId;
|
|
||||||
this.pendingMessage = pendingMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getRegistrationId() {
|
|
||||||
return registrationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PendingMessage getPendingMessage() {
|
|
||||||
return pendingMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDestinationNumber() {
|
|
||||||
return destinationNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDestinationDeviceId() {
|
|
||||||
return destinationDeviceId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,10 +18,10 @@ package org.whispersystems.textsecuregcm.push;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.ApnMessage;
|
||||||
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
|
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
|
||||||
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
import org.whispersystems.textsecuregcm.entities.GcmMessage;
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
|
||||||
@@ -31,62 +31,57 @@ public class PushSender {
|
|||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(PushSender.class);
|
private final Logger logger = LoggerFactory.getLogger(PushSender.class);
|
||||||
|
|
||||||
private final GCMSender gcmSender;
|
private static final String APN_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"},\"content-available\":1,\"category\":\"Signal_Message\"}}";
|
||||||
private final APNSender apnSender;
|
|
||||||
private final WebsocketSender webSocketSender;
|
|
||||||
|
|
||||||
public PushSender(GCMSender gcmClient,
|
private final PushServiceClient pushServiceClient;
|
||||||
APNSender apnSender,
|
private final WebsocketSender webSocketSender;
|
||||||
WebsocketSender websocketSender)
|
|
||||||
{
|
public PushSender(PushServiceClient pushServiceClient, WebsocketSender websocketSender) {
|
||||||
this.gcmSender = gcmClient;
|
this.pushServiceClient = pushServiceClient;
|
||||||
this.apnSender = apnSender;
|
this.webSocketSender = websocketSender;
|
||||||
this.webSocketSender = websocketSender;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(Account account, Device device, OutgoingMessageSignal message)
|
public void sendMessage(Account account, Device device, OutgoingMessageSignal message)
|
||||||
throws NotPushRegisteredException, TransientPushFailureException
|
throws NotPushRegisteredException, TransientPushFailureException
|
||||||
{
|
{
|
||||||
try {
|
if (device.getGcmId() != null) sendGcmMessage(account, device, message);
|
||||||
boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE;
|
else if (device.getApnId() != null) sendApnMessage(account, device, message);
|
||||||
String signalingKey = device.getSignalingKey();
|
else if (device.getFetchesMessages()) sendWebSocketMessage(account, device, message);
|
||||||
EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, signalingKey);
|
else throw new NotPushRegisteredException("No delivery possible!");
|
||||||
PendingMessage pendingMessage = new PendingMessage(message.getSource(),
|
}
|
||||||
message.getTimestamp(),
|
|
||||||
isReceipt,
|
|
||||||
encryptedMessage.serialize());
|
|
||||||
|
|
||||||
sendMessage(account, device, pendingMessage);
|
private void sendGcmMessage(Account account, Device device, OutgoingMessageSignal message)
|
||||||
|
throws TransientPushFailureException, NotPushRegisteredException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String number = account.getNumber();
|
||||||
|
long deviceId = device.getId();
|
||||||
|
String registrationId = device.getGcmId();
|
||||||
|
boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE;
|
||||||
|
EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, device.getSignalingKey());
|
||||||
|
GcmMessage gcmMessage = new GcmMessage(registrationId, number, (int) deviceId,
|
||||||
|
encryptedMessage.toEncodedString(), isReceipt);
|
||||||
|
|
||||||
|
pushServiceClient.send(gcmMessage);
|
||||||
} catch (CryptoEncodingException e) {
|
} catch (CryptoEncodingException e) {
|
||||||
throw new NotPushRegisteredException(e);
|
throw new NotPushRegisteredException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(Account account, Device device, PendingMessage pendingMessage)
|
private void sendApnMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage)
|
||||||
throws NotPushRegisteredException, TransientPushFailureException
|
|
||||||
{
|
|
||||||
if (device.getGcmId() != null) sendGcmMessage(account, device, pendingMessage);
|
|
||||||
else if (device.getApnId() != null) sendApnMessage(account, device, pendingMessage);
|
|
||||||
else if (device.getFetchesMessages()) sendWebSocketMessage(account, device, pendingMessage);
|
|
||||||
else throw new NotPushRegisteredException("No delivery possible!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendGcmMessage(Account account, Device device, PendingMessage pendingMessage) {
|
|
||||||
String number = account.getNumber();
|
|
||||||
long deviceId = device.getId();
|
|
||||||
String registrationId = device.getGcmId();
|
|
||||||
|
|
||||||
gcmSender.sendMessage(number, deviceId, registrationId, pendingMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendApnMessage(Account account, Device device, PendingMessage outgoingMessage)
|
|
||||||
throws TransientPushFailureException
|
throws TransientPushFailureException
|
||||||
{
|
{
|
||||||
apnSender.sendMessage(account, device, device.getApnId(), outgoingMessage);
|
boolean online = webSocketSender.sendMessage(account, device, outgoingMessage, true);
|
||||||
|
|
||||||
|
if (!online && outgoingMessage.getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) {
|
||||||
|
ApnMessage apnMessage = new ApnMessage(device.getApnId(), account.getNumber(),
|
||||||
|
(int)device.getId(), APN_PAYLOAD);
|
||||||
|
pushServiceClient.send(apnMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendWebSocketMessage(Account account, Device device, PendingMessage outgoingMessage)
|
private void sendWebSocketMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage)
|
||||||
{
|
{
|
||||||
webSocketSender.sendMessage(account, device, outgoingMessage);
|
webSocketSender.sendMessage(account, device, outgoingMessage, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.whispersystems.textsecuregcm.push;
|
||||||
|
|
||||||
|
import com.sun.jersey.api.client.Client;
|
||||||
|
import com.sun.jersey.api.client.ClientHandlerException;
|
||||||
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
|
import com.sun.jersey.api.client.UniformInterfaceException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.ApnMessage;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.GcmMessage;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.UnregisteredEvent;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.UnregisteredEventList;
|
||||||
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PushServiceClient {
|
||||||
|
|
||||||
|
private static final String PUSH_GCM_PATH = "/api/v1/push/gcm";
|
||||||
|
private static final String PUSH_APN_PATH = "/api/v1/push/apn";
|
||||||
|
|
||||||
|
private static final String APN_FEEDBACK_PATH = "/api/v1/feedback/apn";
|
||||||
|
private static final String GCM_FEEDBACK_PATH = "/api/v1/feedback/gcm";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(PushServiceClient.class);
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
private final String authorization;
|
||||||
|
|
||||||
|
public PushServiceClient(Client client, PushConfiguration config) {
|
||||||
|
this.client = client;
|
||||||
|
this.host = config.getHost();
|
||||||
|
this.port = config.getPort();
|
||||||
|
this.authorization = getAuthorizationHeader(config.getUsername(), config.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(GcmMessage message) throws TransientPushFailureException {
|
||||||
|
sendPush(PUSH_GCM_PATH, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(ApnMessage message) throws TransientPushFailureException {
|
||||||
|
sendPush(PUSH_APN_PATH, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UnregisteredEvent> getGcmFeedback() throws IOException {
|
||||||
|
return getFeedback(GCM_FEEDBACK_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UnregisteredEvent> getApnFeedback() throws IOException {
|
||||||
|
return getFeedback(APN_FEEDBACK_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPush(String path, Object entity) throws TransientPushFailureException {
|
||||||
|
try {
|
||||||
|
ClientResponse response = client.resource("http://" + host + ":" + port + path)
|
||||||
|
.header("Authorization", authorization)
|
||||||
|
.entity(entity, MediaType.APPLICATION_JSON)
|
||||||
|
.put(ClientResponse.class);
|
||||||
|
|
||||||
|
if (response.getStatus() != 204 && response.getStatus() != 200) {
|
||||||
|
logger.warn("PushServer response: " + response.getStatus() + " " + response.getStatusInfo().getReasonPhrase());
|
||||||
|
throw new TransientPushFailureException("Bad response: " + response.getStatus());
|
||||||
|
}
|
||||||
|
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||||
|
logger.warn("Push error: ", e);
|
||||||
|
throw new TransientPushFailureException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UnregisteredEvent> getFeedback(String path) throws IOException {
|
||||||
|
try {
|
||||||
|
UnregisteredEventList unregisteredEvents = client.resource("http://" + host + ":" + port + path)
|
||||||
|
.header("Authorization", authorization)
|
||||||
|
.get(UnregisteredEventList.class);
|
||||||
|
|
||||||
|
return unregisteredEvents.getDevices();
|
||||||
|
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||||
|
logger.warn("Request error:", e);
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAuthorizationHeader(String username, String password) {
|
||||||
|
return "Basic " + Base64.encodeBytes((username + ":" + password).getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,31 +19,30 @@ package org.whispersystems.textsecuregcm.push;
|
|||||||
import com.codahale.metrics.Meter;
|
import com.codahale.metrics.Meter;
|
||||||
import com.codahale.metrics.MetricRegistry;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
import com.codahale.metrics.SharedMetricRegistries;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
import org.whispersystems.textsecuregcm.util.Constants;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
|
||||||
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||||
|
import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage;
|
||||||
|
|
||||||
public class WebsocketSender {
|
public class WebsocketSender {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(WebsocketSender.class);
|
private static final Logger logger = LoggerFactory.getLogger(WebsocketSender.class);
|
||||||
|
|
||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||||
private final Meter onlineMeter = metricRegistry.meter(name(getClass(), "online"));
|
|
||||||
private final Meter offlineMeter = metricRegistry.meter(name(getClass(), "offline"));
|
|
||||||
|
|
||||||
private static final ObjectMapper mapper = SystemMapper.getMapper();
|
private final Meter websocketOnlineMeter = metricRegistry.meter(name(getClass(), "ws_online" ));
|
||||||
|
private final Meter websocketOfflineMeter = metricRegistry.meter(name(getClass(), "ws_offline" ));
|
||||||
|
|
||||||
|
private final Meter apnOnlineMeter = metricRegistry.meter(name(getClass(), "apn_online" ));
|
||||||
|
private final Meter apnOfflineMeter = metricRegistry.meter(name(getClass(), "apn_offline"));
|
||||||
|
|
||||||
private final StoredMessages storedMessages;
|
private final StoredMessages storedMessages;
|
||||||
private final PubSubManager pubSubManager;
|
private final PubSubManager pubSubManager;
|
||||||
@@ -53,21 +52,28 @@ public class WebsocketSender {
|
|||||||
this.pubSubManager = pubSubManager;
|
this.pubSubManager = pubSubManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(Account account, Device device, PendingMessage pendingMessage) {
|
public boolean sendMessage(Account account, Device device, OutgoingMessageSignal message, boolean apn) {
|
||||||
try {
|
WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId());
|
||||||
String serialized = mapper.writeValueAsString(pendingMessage);
|
PubSubMessage pubSubMessage = PubSubMessage.newBuilder()
|
||||||
WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId());
|
.setType(PubSubMessage.Type.DELIVER)
|
||||||
PubSubMessage pubSubMessage = new PubSubMessage(PubSubMessage.TYPE_DELIVER, serialized);
|
.setContent(message.toByteString())
|
||||||
|
.build();
|
||||||
|
|
||||||
if (pubSubManager.publish(address, pubSubMessage)) {
|
if (pubSubManager.publish(address, pubSubMessage)) {
|
||||||
onlineMeter.mark();
|
if (apn) apnOnlineMeter.mark();
|
||||||
} else {
|
else websocketOnlineMeter.mark();
|
||||||
offlineMeter.mark();
|
|
||||||
storedMessages.insert(address, pendingMessage);
|
return true;
|
||||||
pubSubManager.publish(address, new PubSubMessage(PubSubMessage.TYPE_QUERY_DB, null));
|
} else {
|
||||||
}
|
if (apn) apnOfflineMeter.mark();
|
||||||
} catch (JsonProcessingException e) {
|
else websocketOfflineMeter.mark();
|
||||||
logger.warn("WebsocketSender", "Unable to serialize json", e);
|
|
||||||
|
storedMessages.insert(address, message);
|
||||||
|
pubSubManager.publish(address, PubSubMessage.newBuilder()
|
||||||
|
.setType(PubSubMessage.Type.QUERY_DB)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class Account {
|
|||||||
private String identityKey;
|
private String identityKey;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Optional<Device> authenticatedDevice;
|
private Device authenticatedDevice;
|
||||||
|
|
||||||
public Account() {}
|
public Account() {}
|
||||||
|
|
||||||
@@ -54,11 +54,11 @@ public class Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Device> getAuthenticatedDevice() {
|
public Optional<Device> getAuthenticatedDevice() {
|
||||||
return authenticatedDevice;
|
return Optional.fromNullable(authenticatedDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticatedDevice(Device device) {
|
public void setAuthenticatedDevice(Device device) {
|
||||||
this.authenticatedDevice = Optional.of(device);
|
this.authenticatedDevice = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNumber(String number) {
|
public void setNumber(String number) {
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
|||||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class Device {
|
public class Device {
|
||||||
|
|
||||||
public static final long MASTER_ID = 1;
|
public static final long MASTER_ID = 1;
|
||||||
@@ -46,6 +44,9 @@ public class Device {
|
|||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String apnId;
|
private String apnId;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private long pushTimestamp;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private boolean fetchesMessages;
|
private boolean fetchesMessages;
|
||||||
|
|
||||||
@@ -79,6 +80,10 @@ public class Device {
|
|||||||
|
|
||||||
public void setApnId(String apnId) {
|
public void setApnId(String apnId) {
|
||||||
this.apnId = apnId;
|
this.apnId = apnId;
|
||||||
|
|
||||||
|
if (apnId != null) {
|
||||||
|
this.pushTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGcmId() {
|
public String getGcmId() {
|
||||||
@@ -87,6 +92,10 @@ public class Device {
|
|||||||
|
|
||||||
public void setGcmId(String gcmId) {
|
public void setGcmId(String gcmId) {
|
||||||
this.gcmId = gcmId;
|
this.gcmId = gcmId;
|
||||||
|
|
||||||
|
if (gcmId != null) {
|
||||||
|
this.pushTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
@@ -145,4 +154,8 @@ public class Device {
|
|||||||
public void setSignedPreKey(SignedPreKey signedPreKey) {
|
public void setSignedPreKey(SignedPreKey signedPreKey) {
|
||||||
this.signedPreKey = signedPreKey;
|
this.signedPreKey = signedPreKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPushTimestamp() {
|
||||||
|
return pushTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.gson.Gson;
|
import org.slf4j.Logger;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||||
import org.whispersystems.textsecuregcm.util.IterablePair;
|
import org.whispersystems.textsecuregcm.util.IterablePair;
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -34,12 +39,17 @@ import redis.clients.jedis.Response;
|
|||||||
|
|
||||||
public class DirectoryManager {
|
public class DirectoryManager {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DirectoryManager.class);
|
||||||
|
|
||||||
private static final byte[] DIRECTORY_KEY = {'d', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y'};
|
private static final byte[] DIRECTORY_KEY = {'d', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y'};
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
private final JedisPool redisPool;
|
private final JedisPool redisPool;
|
||||||
|
|
||||||
public DirectoryManager(JedisPool redisPool) {
|
public DirectoryManager(JedisPool redisPool) {
|
||||||
this.redisPool = redisPool;
|
this.redisPool = redisPool;
|
||||||
|
this.objectMapper = new ObjectMapper();
|
||||||
|
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(String number) {
|
public void remove(String number) {
|
||||||
@@ -63,45 +73,48 @@ public class DirectoryManager {
|
|||||||
|
|
||||||
public void add(ClientContact contact) {
|
public void add(ClientContact contact) {
|
||||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||||
Jedis jedis = redisPool.getResource();
|
|
||||||
|
|
||||||
jedis.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
redisPool.returnResource(jedis);
|
jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
logger.warn("JSON Serialization", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(BatchOperationHandle handle, ClientContact contact) {
|
public void add(BatchOperationHandle handle, ClientContact contact) {
|
||||||
Pipeline pipeline = handle.pipeline;
|
try {
|
||||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
Pipeline pipeline = handle.pipeline;
|
||||||
|
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||||
|
|
||||||
pipeline.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
pipeline.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
logger.warn("JSON Serialization", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingClientContact get(BatchOperationHandle handle, byte[] token) {
|
public PendingClientContact get(BatchOperationHandle handle, byte[] token) {
|
||||||
Pipeline pipeline = handle.pipeline;
|
Pipeline pipeline = handle.pipeline;
|
||||||
return new PendingClientContact(token, pipeline.hget(DIRECTORY_KEY, token));
|
return new PendingClientContact(objectMapper, token, pipeline.hget(DIRECTORY_KEY, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ClientContact> get(byte[] token) {
|
public Optional<ClientContact> get(byte[] token) {
|
||||||
Jedis jedis = redisPool.getResource();
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] result = jedis.hget(DIRECTORY_KEY, token);
|
byte[] result = jedis.hget(DIRECTORY_KEY, token);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(result), TokenValue.class);
|
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class);
|
||||||
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
||||||
} finally {
|
} catch (IOException e) {
|
||||||
redisPool.returnResource(jedis);
|
logger.warn("JSON Error", e);
|
||||||
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ClientContact> get(List<byte[]> tokens) {
|
public List<ClientContact> get(List<byte[]> tokens) {
|
||||||
Jedis jedis = redisPool.getResource();
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
|
|
||||||
try {
|
|
||||||
Pipeline pipeline = jedis.pipelined();
|
Pipeline pipeline = jedis.pipelined();
|
||||||
List<Response<byte[]>> futures = new LinkedList<>();
|
List<Response<byte[]>> futures = new LinkedList<>();
|
||||||
List<ClientContact> results = new LinkedList<>();
|
List<ClientContact> results = new LinkedList<>();
|
||||||
@@ -117,17 +130,19 @@ public class DirectoryManager {
|
|||||||
IterablePair<byte[], Response<byte[]>> lists = new IterablePair<>(tokens, futures);
|
IterablePair<byte[], Response<byte[]>> lists = new IterablePair<>(tokens, futures);
|
||||||
|
|
||||||
for (Pair<byte[], Response<byte[]>> pair : lists) {
|
for (Pair<byte[], Response<byte[]>> pair : lists) {
|
||||||
if (pair.second().get() != null) {
|
try {
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(pair.second().get()), TokenValue.class);
|
if (pair.second().get() != null) {
|
||||||
ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.supportsSms);
|
TokenValue tokenValue = objectMapper.readValue(pair.second().get(), TokenValue.class);
|
||||||
|
ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.supportsSms);
|
||||||
|
|
||||||
results.add(clientContact);
|
results.add(clientContact);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Deserialization Problem: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} finally {
|
|
||||||
redisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,12 +171,15 @@ public class DirectoryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class TokenValue {
|
private static class TokenValue {
|
||||||
@SerializedName("r")
|
|
||||||
|
@JsonProperty(value = "r")
|
||||||
private String relay;
|
private String relay;
|
||||||
|
|
||||||
@SerializedName("s")
|
@JsonProperty(value = "s")
|
||||||
private boolean supportsSms;
|
private boolean supportsSms;
|
||||||
|
|
||||||
|
public TokenValue() {}
|
||||||
|
|
||||||
public TokenValue(String relay, boolean supportsSms) {
|
public TokenValue(String relay, boolean supportsSms) {
|
||||||
this.relay = relay;
|
this.relay = relay;
|
||||||
this.supportsSms = supportsSms;
|
this.supportsSms = supportsSms;
|
||||||
@@ -169,22 +187,24 @@ public class DirectoryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class PendingClientContact {
|
public static class PendingClientContact {
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
private final byte[] token;
|
private final byte[] token;
|
||||||
private final Response<byte[]> response;
|
private final Response<byte[]> response;
|
||||||
|
|
||||||
PendingClientContact(byte[] token, Response<byte[]> response) {
|
PendingClientContact(ObjectMapper objectMapper, byte[] token, Response<byte[]> response) {
|
||||||
this.token = token;
|
this.objectMapper = objectMapper;
|
||||||
this.response = response;
|
this.token = token;
|
||||||
|
this.response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ClientContact> get() {
|
public Optional<ClientContact> get() throws IOException {
|
||||||
byte[] result = response.get();
|
byte[] result = response.get();
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(result), TokenValue.class);
|
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class);
|
||||||
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage;
|
||||||
|
|
||||||
public interface PubSubListener {
|
public interface PubSubListener {
|
||||||
|
|
||||||
public void onPubSubMessage(PubSubMessage outgoingMessage);
|
public void onPubSubMessage(PubSubMessage outgoingMessage);
|
||||||
|
|||||||
@@ -1,69 +1,59 @@
|
|||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
|
||||||
import org.whispersystems.textsecuregcm.websocket.InvalidWebsocketAddressException;
|
|
||||||
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage;
|
||||||
|
import redis.clients.jedis.BinaryJedisPubSub;
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.JedisPool;
|
import redis.clients.jedis.JedisPool;
|
||||||
import redis.clients.jedis.JedisPubSub;
|
|
||||||
|
|
||||||
public class PubSubManager {
|
public class PubSubManager {
|
||||||
|
|
||||||
private static final String KEEPALIVE_CHANNEL = "KEEPALIVE";
|
private static final byte[] KEEPALIVE_CHANNEL = "KEEPALIVE".getBytes();
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(PubSubManager.class);
|
private final Logger logger = LoggerFactory.getLogger(PubSubManager.class);
|
||||||
private final ObjectMapper mapper = SystemMapper.getMapper();
|
|
||||||
private final SubscriptionListener baseListener = new SubscriptionListener();
|
private final SubscriptionListener baseListener = new SubscriptionListener();
|
||||||
private final Map<String, PubSubListener> listeners = new HashMap<>();
|
private final Map<String, PubSubListener> listeners = new HashMap<>();
|
||||||
|
|
||||||
private final JedisPool jedisPool;
|
private final JedisPool jedisPool;
|
||||||
private boolean subscribed = false;
|
private boolean subscribed = false;
|
||||||
|
|
||||||
public PubSubManager(final JedisPool jedisPool) {
|
public PubSubManager(JedisPool jedisPool) {
|
||||||
this.jedisPool = jedisPool;
|
this.jedisPool = jedisPool;
|
||||||
initializePubSubWorker();
|
initializePubSubWorker();
|
||||||
waitForSubscription();
|
waitForSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void subscribe(WebsocketAddress address, PubSubListener listener) {
|
public synchronized void subscribe(WebsocketAddress address, PubSubListener listener) {
|
||||||
listeners.put(address.serialize(), listener);
|
String serializedAddress = address.serialize();
|
||||||
baseListener.subscribe(address.serialize());
|
|
||||||
|
listeners.put(serializedAddress, listener);
|
||||||
|
baseListener.subscribe(serializedAddress.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unsubscribe(WebsocketAddress address, PubSubListener listener) {
|
public synchronized void unsubscribe(WebsocketAddress address, PubSubListener listener) {
|
||||||
if (listeners.get(address.serialize()) == listener) {
|
String serializedAddress = address.serialize();
|
||||||
listeners.remove(address.serialize());
|
|
||||||
baseListener.unsubscribe(address.serialize());
|
if (listeners.get(serializedAddress) == listener) {
|
||||||
|
listeners.remove(serializedAddress);
|
||||||
|
baseListener.unsubscribe(serializedAddress.getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean publish(WebsocketAddress address, PubSubMessage message) {
|
public synchronized boolean publish(WebsocketAddress address, PubSubMessage message) {
|
||||||
return publish(address.serialize(), message);
|
return publish(address.serialize().getBytes(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean publish(String channel, PubSubMessage message) {
|
private synchronized boolean publish(byte[] channel, PubSubMessage message) {
|
||||||
try {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
String serialized = mapper.writeValueAsString(message);
|
return jedis.publish(channel, message.toByteArray()) != 0;
|
||||||
Jedis jedis = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
jedis = jedisPool.getResource();
|
|
||||||
return jedis.publish(channel, serialized) != 0;
|
|
||||||
} finally {
|
|
||||||
if (jedis != null)
|
|
||||||
jedisPool.returnResource(jedis);
|
|
||||||
}
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +72,9 @@ public class PubSubManager {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Jedis jedis = null;
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
try {
|
|
||||||
jedis = jedisPool.getResource();
|
|
||||||
jedis.subscribe(baseListener, KEEPALIVE_CHANNEL);
|
jedis.subscribe(baseListener, KEEPALIVE_CHANNEL);
|
||||||
logger.warn("**** Unsubscribed from holding channel!!! ******");
|
logger.warn("**** Unsubscribed from holding channel!!! ******");
|
||||||
} finally {
|
|
||||||
if (jedis != null)
|
|
||||||
jedisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +86,9 @@ public class PubSubManager {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(20000);
|
Thread.sleep(20000);
|
||||||
publish(KEEPALIVE_CHANNEL, new PubSubMessage(0, "foo"));
|
publish(KEEPALIVE_CHANNEL, PubSubMessage.newBuilder()
|
||||||
|
.setType(PubSubMessage.Type.KEEPALIVE)
|
||||||
|
.build());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@@ -110,33 +97,33 @@ public class PubSubManager {
|
|||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SubscriptionListener extends JedisPubSub {
|
private class SubscriptionListener extends BinaryJedisPubSub {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(String channel, String message) {
|
public void onMessage(byte[] channel, byte[] message) {
|
||||||
try {
|
try {
|
||||||
PubSubListener listener;
|
PubSubListener listener;
|
||||||
|
|
||||||
synchronized (PubSubManager.this) {
|
synchronized (PubSubManager.this) {
|
||||||
listener = listeners.get(channel);
|
listener = listeners.get(new String(channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onPubSubMessage(mapper.readValue(message, PubSubMessage.class));
|
listener.onPubSubMessage(PubSubMessage.parseFrom(message));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
logger.warn("IOE", e);
|
logger.warn("Error parsing PubSub protobuf", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPMessage(String s, String s2, String s3) {
|
public void onPMessage(byte[] s, byte[] s2, byte[] s3) {
|
||||||
logger.warn("Received PMessage!");
|
logger.warn("Received PMessage!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(String channel, int count) {
|
public void onSubscribe(byte[] channel, int count) {
|
||||||
if (KEEPALIVE_CHANNEL.equals(channel)) {
|
if (Arrays.equals(KEEPALIVE_CHANNEL, channel)) {
|
||||||
synchronized (PubSubManager.this) {
|
synchronized (PubSubManager.this) {
|
||||||
subscribed = true;
|
subscribed = true;
|
||||||
PubSubManager.this.notifyAll();
|
PubSubManager.this.notifyAll();
|
||||||
@@ -145,12 +132,12 @@ public class PubSubManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnsubscribe(String s, int i) {}
|
public void onUnsubscribe(byte[] s, int i) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPUnsubscribe(String s, int i) {}
|
public void onPUnsubscribe(byte[] s, int i) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPSubscribe(String s, int i) {}
|
public void onPSubscribe(byte[] s, int i) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.whispersystems.textsecuregcm.storage;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
public class PubSubMessage {
|
|
||||||
|
|
||||||
public static final int TYPE_QUERY_DB = 1;
|
|
||||||
public static final int TYPE_DELIVER = 2;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private int type;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String contents;
|
|
||||||
|
|
||||||
public PubSubMessage() {}
|
|
||||||
|
|
||||||
public PubSubMessage(int type, String contents) {
|
|
||||||
this.type = type;
|
|
||||||
this.contents = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContents() {
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
|
// source: PubSubMessage.proto
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
public final class PubSubProtos {
|
||||||
|
private PubSubProtos() {}
|
||||||
|
public static void registerAllExtensions(
|
||||||
|
com.google.protobuf.ExtensionRegistry registry) {
|
||||||
|
}
|
||||||
|
public interface PubSubMessageOrBuilder
|
||||||
|
extends com.google.protobuf.MessageOrBuilder {
|
||||||
|
|
||||||
|
// optional .textsecure.PubSubMessage.Type type = 1;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
boolean hasType();
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type getType();
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
boolean hasContent();
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
com.google.protobuf.ByteString getContent();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Protobuf type {@code textsecure.PubSubMessage}
|
||||||
|
*/
|
||||||
|
public static final class PubSubMessage extends
|
||||||
|
com.google.protobuf.GeneratedMessage
|
||||||
|
implements PubSubMessageOrBuilder {
|
||||||
|
// Use PubSubMessage.newBuilder() to construct.
|
||||||
|
private PubSubMessage(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
|
||||||
|
super(builder);
|
||||||
|
this.unknownFields = builder.getUnknownFields();
|
||||||
|
}
|
||||||
|
private PubSubMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
|
||||||
|
|
||||||
|
private static final PubSubMessage defaultInstance;
|
||||||
|
public static PubSubMessage getDefaultInstance() {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PubSubMessage getDefaultInstanceForType() {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final com.google.protobuf.UnknownFieldSet unknownFields;
|
||||||
|
@java.lang.Override
|
||||||
|
public final com.google.protobuf.UnknownFieldSet
|
||||||
|
getUnknownFields() {
|
||||||
|
return this.unknownFields;
|
||||||
|
}
|
||||||
|
private PubSubMessage(
|
||||||
|
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 8: {
|
||||||
|
int rawValue = input.readEnum();
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type value = org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type.valueOf(rawValue);
|
||||||
|
if (value == null) {
|
||||||
|
unknownFields.mergeVarintField(1, rawValue);
|
||||||
|
} else {
|
||||||
|
bitField0_ |= 0x00000001;
|
||||||
|
type_ = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 18: {
|
||||||
|
bitField0_ |= 0x00000002;
|
||||||
|
content_ = 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.storage.PubSubProtos.internal_static_textsecure_PubSubMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internalGetFieldAccessorTable() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.PubSubProtos.internal_static_textsecure_PubSubMessage_fieldAccessorTable
|
||||||
|
.ensureFieldAccessorsInitialized(
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.class, org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Builder.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.google.protobuf.Parser<PubSubMessage> PARSER =
|
||||||
|
new com.google.protobuf.AbstractParser<PubSubMessage>() {
|
||||||
|
public PubSubMessage parsePartialFrom(
|
||||||
|
com.google.protobuf.CodedInputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return new PubSubMessage(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
public com.google.protobuf.Parser<PubSubMessage> getParserForType() {
|
||||||
|
return PARSER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protobuf enum {@code textsecure.PubSubMessage.Type}
|
||||||
|
*/
|
||||||
|
public enum Type
|
||||||
|
implements com.google.protobuf.ProtocolMessageEnum {
|
||||||
|
/**
|
||||||
|
* <code>UNKNOWN = 0;</code>
|
||||||
|
*/
|
||||||
|
UNKNOWN(0, 0),
|
||||||
|
/**
|
||||||
|
* <code>QUERY_DB = 1;</code>
|
||||||
|
*/
|
||||||
|
QUERY_DB(1, 1),
|
||||||
|
/**
|
||||||
|
* <code>DELIVER = 2;</code>
|
||||||
|
*/
|
||||||
|
DELIVER(2, 2),
|
||||||
|
/**
|
||||||
|
* <code>KEEPALIVE = 3;</code>
|
||||||
|
*/
|
||||||
|
KEEPALIVE(3, 3),
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>UNKNOWN = 0;</code>
|
||||||
|
*/
|
||||||
|
public static final int UNKNOWN_VALUE = 0;
|
||||||
|
/**
|
||||||
|
* <code>QUERY_DB = 1;</code>
|
||||||
|
*/
|
||||||
|
public static final int QUERY_DB_VALUE = 1;
|
||||||
|
/**
|
||||||
|
* <code>DELIVER = 2;</code>
|
||||||
|
*/
|
||||||
|
public static final int DELIVER_VALUE = 2;
|
||||||
|
/**
|
||||||
|
* <code>KEEPALIVE = 3;</code>
|
||||||
|
*/
|
||||||
|
public static final int KEEPALIVE_VALUE = 3;
|
||||||
|
|
||||||
|
|
||||||
|
public final int getNumber() { return value; }
|
||||||
|
|
||||||
|
public static Type valueOf(int value) {
|
||||||
|
switch (value) {
|
||||||
|
case 0: return UNKNOWN;
|
||||||
|
case 1: return QUERY_DB;
|
||||||
|
case 2: return DELIVER;
|
||||||
|
case 3: return KEEPALIVE;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.google.protobuf.Internal.EnumLiteMap<Type>
|
||||||
|
internalGetValueMap() {
|
||||||
|
return internalValueMap;
|
||||||
|
}
|
||||||
|
private static com.google.protobuf.Internal.EnumLiteMap<Type>
|
||||||
|
internalValueMap =
|
||||||
|
new com.google.protobuf.Internal.EnumLiteMap<Type>() {
|
||||||
|
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.storage.PubSubProtos.PubSubMessage.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.PubSubMessage.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bitField0_;
|
||||||
|
// optional .textsecure.PubSubMessage.Type type = 1;
|
||||||
|
public static final int TYPE_FIELD_NUMBER = 1;
|
||||||
|
private org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type type_;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasType() {
|
||||||
|
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type getType() {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
public static final int CONTENT_FIELD_NUMBER = 2;
|
||||||
|
private com.google.protobuf.ByteString content_;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasContent() {
|
||||||
|
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public com.google.protobuf.ByteString getContent() {
|
||||||
|
return content_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFields() {
|
||||||
|
type_ = org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type.UNKNOWN;
|
||||||
|
content_ = 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.writeEnum(1, type_.getNumber());
|
||||||
|
}
|
||||||
|
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||||
|
output.writeBytes(2, content_);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
.computeEnumSize(1, type_.getNumber());
|
||||||
|
}
|
||||||
|
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeBytesSize(2, content_);
|
||||||
|
}
|
||||||
|
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.storage.PubSubProtos.PubSubMessage parseFrom(
|
||||||
|
com.google.protobuf.ByteString data)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage 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.storage.PubSubProtos.PubSubMessage parseFrom(byte[] data)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseFrom(
|
||||||
|
byte[] data,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseFrom(java.io.InputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseFrom(
|
||||||
|
java.io.InputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseDelimitedFrom(java.io.InputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseDelimitedFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseDelimitedFrom(
|
||||||
|
java.io.InputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseDelimitedFrom(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage parseFrom(
|
||||||
|
com.google.protobuf.CodedInputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage 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.storage.PubSubProtos.PubSubMessage 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.PubSubMessage}
|
||||||
|
*/
|
||||||
|
public static final class Builder extends
|
||||||
|
com.google.protobuf.GeneratedMessage.Builder<Builder>
|
||||||
|
implements org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessageOrBuilder {
|
||||||
|
public static final com.google.protobuf.Descriptors.Descriptor
|
||||||
|
getDescriptor() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.PubSubProtos.internal_static_textsecure_PubSubMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internalGetFieldAccessorTable() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.PubSubProtos.internal_static_textsecure_PubSubMessage_fieldAccessorTable
|
||||||
|
.ensureFieldAccessorsInitialized(
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.class, org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Builder.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct using org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.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_ = org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type.UNKNOWN;
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
|
content_ = com.google.protobuf.ByteString.EMPTY;
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000002);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder clone() {
|
||||||
|
return create().mergeFrom(buildPartial());
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.google.protobuf.Descriptors.Descriptor
|
||||||
|
getDescriptorForType() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.PubSubProtos.internal_static_textsecure_PubSubMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage getDefaultInstanceForType() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.getDefaultInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage build() {
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage result = buildPartial();
|
||||||
|
if (!result.isInitialized()) {
|
||||||
|
throw newUninitializedMessageException(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage buildPartial() {
|
||||||
|
org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage result = new org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage(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.content_ = content_;
|
||||||
|
result.bitField0_ = to_bitField0_;
|
||||||
|
onBuilt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder mergeFrom(com.google.protobuf.Message other) {
|
||||||
|
if (other instanceof org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage) {
|
||||||
|
return mergeFrom((org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage)other);
|
||||||
|
} else {
|
||||||
|
super.mergeFrom(other);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder mergeFrom(org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage other) {
|
||||||
|
if (other == org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.getDefaultInstance()) return this;
|
||||||
|
if (other.hasType()) {
|
||||||
|
setType(other.getType());
|
||||||
|
}
|
||||||
|
if (other.hasContent()) {
|
||||||
|
setContent(other.getContent());
|
||||||
|
}
|
||||||
|
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.storage.PubSubProtos.PubSubMessage parsedMessage = null;
|
||||||
|
try {
|
||||||
|
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
|
||||||
|
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
|
||||||
|
parsedMessage = (org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage) e.getUnfinishedMessage();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (parsedMessage != null) {
|
||||||
|
mergeFrom(parsedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
private int bitField0_;
|
||||||
|
|
||||||
|
// optional .textsecure.PubSubMessage.Type type = 1;
|
||||||
|
private org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type type_ = org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type.UNKNOWN;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasType() {
|
||||||
|
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type getType() {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public Builder setType(org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
bitField0_ |= 0x00000001;
|
||||||
|
type_ = value;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.PubSubMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public Builder clearType() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
|
type_ = org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage.Type.UNKNOWN;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
private com.google.protobuf.ByteString content_ = com.google.protobuf.ByteString.EMPTY;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasContent() {
|
||||||
|
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public com.google.protobuf.ByteString getContent() {
|
||||||
|
return content_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public Builder setContent(com.google.protobuf.ByteString value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
bitField0_ |= 0x00000002;
|
||||||
|
content_ = value;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public Builder clearContent() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000002);
|
||||||
|
content_ = getDefaultInstance().getContent();
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(builder_scope:textsecure.PubSubMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
defaultInstance = new PubSubMessage(true);
|
||||||
|
defaultInstance.initFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(class_scope:textsecure.PubSubMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static com.google.protobuf.Descriptors.Descriptor
|
||||||
|
internal_static_textsecure_PubSubMessage_descriptor;
|
||||||
|
private static
|
||||||
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internal_static_textsecure_PubSubMessage_fieldAccessorTable;
|
||||||
|
|
||||||
|
public static com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
getDescriptor() {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
private static com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
descriptor;
|
||||||
|
static {
|
||||||
|
java.lang.String[] descriptorData = {
|
||||||
|
"\n\023PubSubMessage.proto\022\ntextsecure\"\215\001\n\rPu" +
|
||||||
|
"bSubMessage\022,\n\004type\030\001 \001(\0162\036.textsecure.P" +
|
||||||
|
"ubSubMessage.Type\022\017\n\007content\030\002 \001(\014\"=\n\004Ty" +
|
||||||
|
"pe\022\013\n\007UNKNOWN\020\000\022\014\n\010QUERY_DB\020\001\022\013\n\007DELIVER" +
|
||||||
|
"\020\002\022\r\n\tKEEPALIVE\020\003B8\n(org.whispersystems." +
|
||||||
|
"textsecuregcm.storageB\014PubSubProtos"
|
||||||
|
};
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
public com.google.protobuf.ExtensionRegistry assignDescriptors(
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor root) {
|
||||||
|
descriptor = root;
|
||||||
|
internal_static_textsecure_PubSubMessage_descriptor =
|
||||||
|
getDescriptor().getMessageTypes().get(0);
|
||||||
|
internal_static_textsecure_PubSubMessage_fieldAccessorTable = new
|
||||||
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||||
|
internal_static_textsecure_PubSubMessage_descriptor,
|
||||||
|
new java.lang.String[] { "Type", "Content", });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
.internalBuildGeneratedFileFrom(descriptorData,
|
||||||
|
new com.google.protobuf.Descriptors.FileDescriptor[] {
|
||||||
|
}, assigner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(outer_class_scope)
|
||||||
|
}
|
||||||
@@ -0,0 +1,624 @@
|
|||||||
|
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
|
// source: StoredMessage.proto
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
public final class StoredMessageProtos {
|
||||||
|
private StoredMessageProtos() {}
|
||||||
|
public static void registerAllExtensions(
|
||||||
|
com.google.protobuf.ExtensionRegistry registry) {
|
||||||
|
}
|
||||||
|
public interface StoredMessageOrBuilder
|
||||||
|
extends com.google.protobuf.MessageOrBuilder {
|
||||||
|
|
||||||
|
// optional .textsecure.StoredMessage.Type type = 1;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
boolean hasType();
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type getType();
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
boolean hasContent();
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
com.google.protobuf.ByteString getContent();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Protobuf type {@code textsecure.StoredMessage}
|
||||||
|
*/
|
||||||
|
public static final class StoredMessage extends
|
||||||
|
com.google.protobuf.GeneratedMessage
|
||||||
|
implements StoredMessageOrBuilder {
|
||||||
|
// Use StoredMessage.newBuilder() to construct.
|
||||||
|
private StoredMessage(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
|
||||||
|
super(builder);
|
||||||
|
this.unknownFields = builder.getUnknownFields();
|
||||||
|
}
|
||||||
|
private StoredMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
|
||||||
|
|
||||||
|
private static final StoredMessage defaultInstance;
|
||||||
|
public static StoredMessage getDefaultInstance() {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoredMessage getDefaultInstanceForType() {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final com.google.protobuf.UnknownFieldSet unknownFields;
|
||||||
|
@java.lang.Override
|
||||||
|
public final com.google.protobuf.UnknownFieldSet
|
||||||
|
getUnknownFields() {
|
||||||
|
return this.unknownFields;
|
||||||
|
}
|
||||||
|
private StoredMessage(
|
||||||
|
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 8: {
|
||||||
|
int rawValue = input.readEnum();
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type value = org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type.valueOf(rawValue);
|
||||||
|
if (value == null) {
|
||||||
|
unknownFields.mergeVarintField(1, rawValue);
|
||||||
|
} else {
|
||||||
|
bitField0_ |= 0x00000001;
|
||||||
|
type_ = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 18: {
|
||||||
|
bitField0_ |= 0x00000002;
|
||||||
|
content_ = 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.storage.StoredMessageProtos.internal_static_textsecure_StoredMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internalGetFieldAccessorTable() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.StoredMessageProtos.internal_static_textsecure_StoredMessage_fieldAccessorTable
|
||||||
|
.ensureFieldAccessorsInitialized(
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.class, org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Builder.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.google.protobuf.Parser<StoredMessage> PARSER =
|
||||||
|
new com.google.protobuf.AbstractParser<StoredMessage>() {
|
||||||
|
public StoredMessage parsePartialFrom(
|
||||||
|
com.google.protobuf.CodedInputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return new StoredMessage(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
public com.google.protobuf.Parser<StoredMessage> getParserForType() {
|
||||||
|
return PARSER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protobuf enum {@code textsecure.StoredMessage.Type}
|
||||||
|
*/
|
||||||
|
public enum Type
|
||||||
|
implements com.google.protobuf.ProtocolMessageEnum {
|
||||||
|
/**
|
||||||
|
* <code>UNKNOWN = 0;</code>
|
||||||
|
*/
|
||||||
|
UNKNOWN(0, 0),
|
||||||
|
/**
|
||||||
|
* <code>MESSAGE = 1;</code>
|
||||||
|
*/
|
||||||
|
MESSAGE(1, 1),
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>UNKNOWN = 0;</code>
|
||||||
|
*/
|
||||||
|
public static final int UNKNOWN_VALUE = 0;
|
||||||
|
/**
|
||||||
|
* <code>MESSAGE = 1;</code>
|
||||||
|
*/
|
||||||
|
public static final int MESSAGE_VALUE = 1;
|
||||||
|
|
||||||
|
|
||||||
|
public final int getNumber() { return value; }
|
||||||
|
|
||||||
|
public static Type valueOf(int value) {
|
||||||
|
switch (value) {
|
||||||
|
case 0: return UNKNOWN;
|
||||||
|
case 1: return MESSAGE;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static com.google.protobuf.Internal.EnumLiteMap<Type>
|
||||||
|
internalGetValueMap() {
|
||||||
|
return internalValueMap;
|
||||||
|
}
|
||||||
|
private static com.google.protobuf.Internal.EnumLiteMap<Type>
|
||||||
|
internalValueMap =
|
||||||
|
new com.google.protobuf.Internal.EnumLiteMap<Type>() {
|
||||||
|
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.storage.StoredMessageProtos.StoredMessage.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.StoredMessage.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bitField0_;
|
||||||
|
// optional .textsecure.StoredMessage.Type type = 1;
|
||||||
|
public static final int TYPE_FIELD_NUMBER = 1;
|
||||||
|
private org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type type_;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasType() {
|
||||||
|
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type getType() {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
public static final int CONTENT_FIELD_NUMBER = 2;
|
||||||
|
private com.google.protobuf.ByteString content_;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasContent() {
|
||||||
|
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public com.google.protobuf.ByteString getContent() {
|
||||||
|
return content_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFields() {
|
||||||
|
type_ = org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type.UNKNOWN;
|
||||||
|
content_ = 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.writeEnum(1, type_.getNumber());
|
||||||
|
}
|
||||||
|
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||||
|
output.writeBytes(2, content_);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
.computeEnumSize(1, type_.getNumber());
|
||||||
|
}
|
||||||
|
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeBytesSize(2, content_);
|
||||||
|
}
|
||||||
|
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.storage.StoredMessageProtos.StoredMessage parseFrom(
|
||||||
|
com.google.protobuf.ByteString data)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage 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.storage.StoredMessageProtos.StoredMessage parseFrom(byte[] data)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseFrom(
|
||||||
|
byte[] data,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||||
|
return PARSER.parseFrom(data, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseFrom(java.io.InputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseFrom(
|
||||||
|
java.io.InputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseDelimitedFrom(java.io.InputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseDelimitedFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseDelimitedFrom(
|
||||||
|
java.io.InputStream input,
|
||||||
|
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseDelimitedFrom(input, extensionRegistry);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage parseFrom(
|
||||||
|
com.google.protobuf.CodedInputStream input)
|
||||||
|
throws java.io.IOException {
|
||||||
|
return PARSER.parseFrom(input);
|
||||||
|
}
|
||||||
|
public static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage 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.storage.StoredMessageProtos.StoredMessage 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.StoredMessage}
|
||||||
|
*/
|
||||||
|
public static final class Builder extends
|
||||||
|
com.google.protobuf.GeneratedMessage.Builder<Builder>
|
||||||
|
implements org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessageOrBuilder {
|
||||||
|
public static final com.google.protobuf.Descriptors.Descriptor
|
||||||
|
getDescriptor() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.StoredMessageProtos.internal_static_textsecure_StoredMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internalGetFieldAccessorTable() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.StoredMessageProtos.internal_static_textsecure_StoredMessage_fieldAccessorTable
|
||||||
|
.ensureFieldAccessorsInitialized(
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.class, org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Builder.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct using org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.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_ = org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type.UNKNOWN;
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
|
content_ = com.google.protobuf.ByteString.EMPTY;
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000002);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder clone() {
|
||||||
|
return create().mergeFrom(buildPartial());
|
||||||
|
}
|
||||||
|
|
||||||
|
public com.google.protobuf.Descriptors.Descriptor
|
||||||
|
getDescriptorForType() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.StoredMessageProtos.internal_static_textsecure_StoredMessage_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage getDefaultInstanceForType() {
|
||||||
|
return org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.getDefaultInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage build() {
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage result = buildPartial();
|
||||||
|
if (!result.isInitialized()) {
|
||||||
|
throw newUninitializedMessageException(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage buildPartial() {
|
||||||
|
org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage result = new org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage(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.content_ = content_;
|
||||||
|
result.bitField0_ = to_bitField0_;
|
||||||
|
onBuilt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder mergeFrom(com.google.protobuf.Message other) {
|
||||||
|
if (other instanceof org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage) {
|
||||||
|
return mergeFrom((org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage)other);
|
||||||
|
} else {
|
||||||
|
super.mergeFrom(other);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder mergeFrom(org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage other) {
|
||||||
|
if (other == org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.getDefaultInstance()) return this;
|
||||||
|
if (other.hasType()) {
|
||||||
|
setType(other.getType());
|
||||||
|
}
|
||||||
|
if (other.hasContent()) {
|
||||||
|
setContent(other.getContent());
|
||||||
|
}
|
||||||
|
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.storage.StoredMessageProtos.StoredMessage parsedMessage = null;
|
||||||
|
try {
|
||||||
|
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
|
||||||
|
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
|
||||||
|
parsedMessage = (org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage) e.getUnfinishedMessage();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (parsedMessage != null) {
|
||||||
|
mergeFrom(parsedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
private int bitField0_;
|
||||||
|
|
||||||
|
// optional .textsecure.StoredMessage.Type type = 1;
|
||||||
|
private org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type type_ = org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type.UNKNOWN;
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasType() {
|
||||||
|
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type getType() {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public Builder setType(org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
bitField0_ |= 0x00000001;
|
||||||
|
type_ = value;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional .textsecure.StoredMessage.Type type = 1;</code>
|
||||||
|
*/
|
||||||
|
public Builder clearType() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000001);
|
||||||
|
type_ = org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage.Type.UNKNOWN;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional bytes content = 2;
|
||||||
|
private com.google.protobuf.ByteString content_ = com.google.protobuf.ByteString.EMPTY;
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public boolean hasContent() {
|
||||||
|
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public com.google.protobuf.ByteString getContent() {
|
||||||
|
return content_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public Builder setContent(com.google.protobuf.ByteString value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
bitField0_ |= 0x00000002;
|
||||||
|
content_ = value;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <code>optional bytes content = 2;</code>
|
||||||
|
*/
|
||||||
|
public Builder clearContent() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000002);
|
||||||
|
content_ = getDefaultInstance().getContent();
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(builder_scope:textsecure.StoredMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
defaultInstance = new StoredMessage(true);
|
||||||
|
defaultInstance.initFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(class_scope:textsecure.StoredMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static com.google.protobuf.Descriptors.Descriptor
|
||||||
|
internal_static_textsecure_StoredMessage_descriptor;
|
||||||
|
private static
|
||||||
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||||
|
internal_static_textsecure_StoredMessage_fieldAccessorTable;
|
||||||
|
|
||||||
|
public static com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
getDescriptor() {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
private static com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
descriptor;
|
||||||
|
static {
|
||||||
|
java.lang.String[] descriptorData = {
|
||||||
|
"\n\023StoredMessage.proto\022\ntextsecure\"p\n\rSto" +
|
||||||
|
"redMessage\022,\n\004type\030\001 \001(\0162\036.textsecure.St" +
|
||||||
|
"oredMessage.Type\022\017\n\007content\030\002 \001(\014\" \n\004Typ" +
|
||||||
|
"e\022\013\n\007UNKNOWN\020\000\022\013\n\007MESSAGE\020\001B?\n(org.whisp" +
|
||||||
|
"ersystems.textsecuregcm.storageB\023StoredM" +
|
||||||
|
"essageProtos"
|
||||||
|
};
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
public com.google.protobuf.ExtensionRegistry assignDescriptors(
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor root) {
|
||||||
|
descriptor = root;
|
||||||
|
internal_static_textsecure_StoredMessage_descriptor =
|
||||||
|
getDescriptor().getMessageTypes().get(0);
|
||||||
|
internal_static_textsecure_StoredMessage_fieldAccessorTable = new
|
||||||
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||||
|
internal_static_textsecure_StoredMessage_descriptor,
|
||||||
|
new java.lang.String[] { "Type", "Content", });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
com.google.protobuf.Descriptors.FileDescriptor
|
||||||
|
.internalBuildGeneratedFileFrom(descriptorData,
|
||||||
|
new com.google.protobuf.Descriptors.FileDescriptor[] {
|
||||||
|
}, assigner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@protoc_insertion_point(outer_class_scope)
|
||||||
|
}
|
||||||
@@ -19,21 +19,19 @@ package org.whispersystems.textsecuregcm.storage;
|
|||||||
import com.codahale.metrics.Histogram;
|
import com.codahale.metrics.Histogram;
|
||||||
import com.codahale.metrics.MetricRegistry;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.codahale.metrics.SharedMetricRegistries;
|
import com.codahale.metrics.SharedMetricRegistries;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Constants;
|
import org.whispersystems.textsecuregcm.util.Constants;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
|
||||||
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||||
|
import static org.whispersystems.textsecuregcm.storage.StoredMessageProtos.StoredMessage;
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.JedisPool;
|
import redis.clients.jedis.JedisPool;
|
||||||
|
|
||||||
@@ -44,8 +42,6 @@ public class StoredMessages {
|
|||||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||||
private final Histogram queueSizeHistogram = metricRegistry.histogram(name(getClass(), "queue_size"));
|
private final Histogram queueSizeHistogram = metricRegistry.histogram(name(getClass(), "queue_size"));
|
||||||
|
|
||||||
|
|
||||||
private static final ObjectMapper mapper = SystemMapper.getMapper();
|
|
||||||
private static final String QUEUE_PREFIX = "msgs";
|
private static final String QUEUE_PREFIX = "msgs";
|
||||||
|
|
||||||
private final JedisPool jedisPool;
|
private final JedisPool jedisPool;
|
||||||
@@ -55,64 +51,57 @@ public class StoredMessages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clear(WebsocketAddress address) {
|
public void clear(WebsocketAddress address) {
|
||||||
Jedis jedis = null;
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
|
||||||
try {
|
|
||||||
jedis = jedisPool.getResource();
|
|
||||||
jedis.del(getKey(address));
|
jedis.del(getKey(address));
|
||||||
} finally {
|
|
||||||
if (jedis != null)
|
|
||||||
jedisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(WebsocketAddress address, PendingMessage message) {
|
public void insert(WebsocketAddress address, OutgoingMessageSignal message) {
|
||||||
Jedis jedis = null;
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
byte[] queue = getKey(address);
|
||||||
|
StoredMessage storedMessage = StoredMessage.newBuilder()
|
||||||
|
.setType(StoredMessage.Type.MESSAGE)
|
||||||
|
.setContent(message.toByteString())
|
||||||
|
.build();
|
||||||
|
|
||||||
try {
|
long queueSize = jedis.lpush(queue, storedMessage.toByteArray());
|
||||||
jedis = jedisPool.getResource();
|
|
||||||
|
|
||||||
String serializedMessage = mapper.writeValueAsString(message);
|
|
||||||
long queueSize = jedis.lpush(getKey(address), serializedMessage);
|
|
||||||
queueSizeHistogram.update(queueSize);
|
queueSizeHistogram.update(queueSize);
|
||||||
|
|
||||||
|
jedis.expireAt(queue, (System.currentTimeMillis() / 1000) + TimeUnit.DAYS.toSeconds(30));
|
||||||
|
|
||||||
if (queueSize > 1000) {
|
if (queueSize > 1000) {
|
||||||
jedis.ltrim(getKey(address), 0, 999);
|
jedis.ltrim(getKey(address), 0, 999);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
logger.warn("StoredMessages", "Unable to store correctly", e);
|
|
||||||
} finally {
|
|
||||||
if (jedis != null)
|
|
||||||
jedisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PendingMessage> getMessagesForDevice(WebsocketAddress address) {
|
public List<OutgoingMessageSignal> getMessagesForDevice(WebsocketAddress address) {
|
||||||
List<PendingMessage> messages = new LinkedList<>();
|
List<OutgoingMessageSignal> messages = new LinkedList<>();
|
||||||
Jedis jedis = null;
|
|
||||||
|
|
||||||
try {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis = jedisPool.getResource();
|
byte[] message;
|
||||||
String message;
|
|
||||||
|
|
||||||
while ((message = jedis.rpop(getKey(address))) != null) {
|
while ((message = jedis.rpop(getKey(address))) != null) {
|
||||||
try {
|
try {
|
||||||
messages.add(mapper.readValue(message, PendingMessage.class));
|
StoredMessage storedMessage = StoredMessage.parseFrom(message);
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("StoredMessages", "Not a valid PendingMessage", e);
|
if (storedMessage.getType().getNumber() == StoredMessage.Type.MESSAGE_VALUE) {
|
||||||
|
messages.add(OutgoingMessageSignal.parseFrom(storedMessage.getContent()));
|
||||||
|
} else {
|
||||||
|
logger.warn("Unkown stored message type: " + storedMessage.getType().getNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
logger.warn("Error parsing protobuf", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
} finally {
|
|
||||||
if (jedis != null)
|
|
||||||
jedisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getKey(WebsocketAddress address) {
|
private byte[] getKey(WebsocketAddress address) {
|
||||||
return QUEUE_PREFIX + ":" + address.serialize();
|
return (QUEUE_PREFIX + ":" + address.serialize()).getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.google.common.base.Optional;
|
|||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.websocket.auth.AuthenticationException;
|
import org.whispersystems.websocket.auth.AuthenticationException;
|
||||||
import org.whispersystems.websocket.auth.WebSocketAuthenticator;
|
import org.whispersystems.websocket.auth.WebSocketAuthenticator;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package org.whispersystems.textsecuregcm.websocket;
|
package org.whispersystems.textsecuregcm.websocket;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||||
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
||||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||||
import org.whispersystems.textsecuregcm.push.TransientPushFailureException;
|
import org.whispersystems.textsecuregcm.push.TransientPushFailureException;
|
||||||
@@ -16,23 +17,20 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
|||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubListener;
|
import org.whispersystems.textsecuregcm.storage.PubSubListener;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
|
||||||
import org.whispersystems.websocket.WebSocketClient;
|
import org.whispersystems.websocket.WebSocketClient;
|
||||||
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
|
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||||
|
import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage;
|
||||||
|
|
||||||
public class WebSocketConnection implements PubSubListener {
|
public class WebSocketConnection implements PubSubListener {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
|
private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
|
||||||
private static final ObjectMapper objectMapper = SystemMapper.getMapper();
|
|
||||||
|
|
||||||
private final AccountsManager accountsManager;
|
private final AccountsManager accountsManager;
|
||||||
private final PushSender pushSender;
|
private final PushSender pushSender;
|
||||||
@@ -72,52 +70,56 @@ public class WebSocketConnection implements PubSubListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPubSubMessage(PubSubMessage message) {
|
public void onPubSubMessage(PubSubMessage pubSubMessage) {
|
||||||
try {
|
try {
|
||||||
switch (message.getType()) {
|
switch (pubSubMessage.getType().getNumber()) {
|
||||||
case PubSubMessage.TYPE_QUERY_DB:
|
case PubSubMessage.Type.QUERY_DB_VALUE:
|
||||||
processStoredMessages();
|
processStoredMessages();
|
||||||
break;
|
break;
|
||||||
case PubSubMessage.TYPE_DELIVER:
|
case PubSubMessage.Type.DELIVER_VALUE:
|
||||||
PendingMessage pendingMessage = objectMapper.readValue(message.getContents(),
|
sendMessage(OutgoingMessageSignal.parseFrom(pubSubMessage.getContent()));
|
||||||
PendingMessage.class);
|
|
||||||
sendMessage(pendingMessage);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.warn("Unknown pubsub message: " + message.getType());
|
logger.warn("Unknown pubsub message: " + pubSubMessage.getType().getNumber());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
logger.warn("Error deserializing PendingMessage", e);
|
logger.warn("Protobuf parse error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(final PendingMessage message) {
|
private void sendMessage(final OutgoingMessageSignal message) {
|
||||||
String content = message.getEncryptedOutgoingMessage();
|
try {
|
||||||
Optional<byte[]> body = Optional.fromNullable(content.getBytes());
|
EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, device.getSignalingKey());
|
||||||
ListenableFuture<WebSocketResponseMessage> response = client.sendRequest("PUT", "/api/v1/message", body);
|
Optional<byte[]> body = Optional.fromNullable(encryptedMessage.toByteArray());
|
||||||
|
ListenableFuture<WebSocketResponseMessage> response = client.sendRequest("PUT", "/api/v1/message", body);
|
||||||
|
|
||||||
Futures.addCallback(response, new FutureCallback<WebSocketResponseMessage>() {
|
Futures.addCallback(response, new FutureCallback<WebSocketResponseMessage>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@Nullable WebSocketResponseMessage response) {
|
public void onSuccess(@Nullable WebSocketResponseMessage response) {
|
||||||
if (isSuccessResponse(response) && !message.isReceipt()) {
|
boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE;
|
||||||
sendDeliveryReceiptFor(message);
|
|
||||||
} else if (!isSuccessResponse(response)) {
|
if (isSuccessResponse(response) && !isReceipt) {
|
||||||
|
sendDeliveryReceiptFor(message);
|
||||||
|
} else if (!isSuccessResponse(response)) {
|
||||||
|
requeueMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@Nonnull Throwable throwable) {
|
||||||
requeueMessage(message);
|
requeueMessage(message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private boolean isSuccessResponse(WebSocketResponseMessage response) {
|
||||||
public void onFailure(@Nonnull Throwable throwable) {
|
return response != null && response.getStatus() >= 200 && response.getStatus() < 300;
|
||||||
requeueMessage(message);
|
}
|
||||||
}
|
});
|
||||||
|
} catch (CryptoEncodingException e) {
|
||||||
private boolean isSuccessResponse(WebSocketResponseMessage response) {
|
logger.warn("Bad signaling key", e);
|
||||||
return response != null && response.getStatus() >= 200 && response.getStatus() < 300;
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requeueMessage(PendingMessage message) {
|
private void requeueMessage(OutgoingMessageSignal message) {
|
||||||
try {
|
try {
|
||||||
pushSender.sendMessage(account, device, message);
|
pushSender.sendMessage(account, device, message);
|
||||||
} catch (NotPushRegisteredException | TransientPushFailureException e) {
|
} catch (NotPushRegisteredException | TransientPushFailureException e) {
|
||||||
@@ -126,12 +128,12 @@ public class WebSocketConnection implements PubSubListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDeliveryReceiptFor(PendingMessage message) {
|
private void sendDeliveryReceiptFor(OutgoingMessageSignal message) {
|
||||||
try {
|
try {
|
||||||
Optional<Account> source = accountsManager.get(message.getSender());
|
Optional<Account> source = accountsManager.get(message.getSource());
|
||||||
|
|
||||||
if (!source.isPresent()) {
|
if (!source.isPresent()) {
|
||||||
logger.warn("Source account disappeared? (%s)", message.getSender());
|
logger.warn("Source account disappeared? (%s)", message.getSource());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +141,7 @@ public class WebSocketConnection implements PubSubListener {
|
|||||||
OutgoingMessageSignal.newBuilder()
|
OutgoingMessageSignal.newBuilder()
|
||||||
.setSource(account.getNumber())
|
.setSource(account.getNumber())
|
||||||
.setSourceDevice((int) device.getId())
|
.setSourceDevice((int) device.getId())
|
||||||
.setTimestamp(message.getMessageId())
|
.setTimestamp(message.getTimestamp())
|
||||||
.setType(OutgoingMessageSignal.Type.RECEIPT_VALUE);
|
.setType(OutgoingMessageSignal.Type.RECEIPT_VALUE);
|
||||||
|
|
||||||
for (Device device : source.get().getDevices()) {
|
for (Device device : source.get().getDevices()) {
|
||||||
@@ -151,9 +153,9 @@ public class WebSocketConnection implements PubSubListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processStoredMessages() {
|
private void processStoredMessages() {
|
||||||
List<PendingMessage> messages = storedMessages.getMessagesForDevice(address);
|
List<OutgoingMessageSignal> messages = storedMessages.getMessagesForDevice(address);
|
||||||
|
|
||||||
for (PendingMessage message : messages) {
|
for (OutgoingMessageSignal message : messages) {
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class DirectoryCommand extends ConfiguredCommand<WhisperServerConfigurati
|
|||||||
|
|
||||||
Accounts accounts = dbi.onDemand(Accounts.class);
|
Accounts accounts = dbi.onDemand(Accounts.class);
|
||||||
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
||||||
JedisPool redisClient = new RedisClientFactory(config.getRedisConfiguration()).getRedisClientPool();
|
JedisPool redisClient = new RedisClientFactory(config.getDirectoryConfiguration().getUrl()).getRedisClientPool();
|
||||||
DirectoryManager directory = new DirectoryManager(redisClient);
|
DirectoryManager directory = new DirectoryManager(redisClient);
|
||||||
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
||||||
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationH
|
|||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -37,6 +38,8 @@ import static org.whispersystems.textsecuregcm.storage.DirectoryManager.PendingC
|
|||||||
|
|
||||||
public class DirectoryUpdater {
|
public class DirectoryUpdater {
|
||||||
|
|
||||||
|
private static final int CHUNK_SIZE = 10000;
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(DirectoryUpdater.class);
|
private final Logger logger = LoggerFactory.getLogger(DirectoryUpdater.class);
|
||||||
|
|
||||||
private final AccountsManager accountsManager;
|
private final AccountsManager accountsManager;
|
||||||
@@ -53,33 +56,40 @@ public class DirectoryUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateFromLocalDatabase() {
|
public void updateFromLocalDatabase() {
|
||||||
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
int contactsAdded = 0;
|
||||||
|
int contactsRemoved = 0;
|
||||||
|
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Iterator<Account> accounts = accountsManager.getAll();
|
logger.info("Updating from local DB.");
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
if (accounts == null)
|
for (;;) {
|
||||||
return;
|
List<Account> accounts = accountsManager.getAll(offset, CHUNK_SIZE);
|
||||||
|
|
||||||
while (accounts.hasNext()) {
|
if (accounts == null || accounts.isEmpty()) break;
|
||||||
Account account = accounts.next();
|
else offset += accounts.size();
|
||||||
|
|
||||||
if (account.isActive()) {
|
for (Account account : accounts) {
|
||||||
byte[] token = Util.getContactToken(account.getNumber());
|
if (account.isActive()) {
|
||||||
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
byte[] token = Util.getContactToken(account.getNumber());
|
||||||
|
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
||||||
|
|
||||||
directory.add(batchOperation, clientContact);
|
directory.add(batchOperation, clientContact);
|
||||||
|
contactsAdded++;
|
||||||
logger.debug("Adding local token: " + Base64.encodeBytesWithoutPadding(token));
|
} else {
|
||||||
} else {
|
directory.remove(batchOperation, account.getNumber());
|
||||||
directory.remove(batchOperation, account.getNumber());
|
contactsRemoved++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("Processed " + CHUNK_SIZE + " local accounts...");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
directory.stopBatchOperation(batchOperation);
|
directory.stopBatchOperation(batchOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Local directory is updated.");
|
logger.info(String.format("Local directory is updated (%d added, %d removed).", contactsAdded, contactsRemoved));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateFromPeers() {
|
public void updateFromPeers() {
|
||||||
@@ -121,19 +131,23 @@ public class DirectoryUpdater {
|
|||||||
Iterator<PendingClientContact> localContactIterator = localContacts.iterator();
|
Iterator<PendingClientContact> localContactIterator = localContacts.iterator();
|
||||||
|
|
||||||
while (remoteContactIterator.hasNext() && localContactIterator.hasNext()) {
|
while (remoteContactIterator.hasNext() && localContactIterator.hasNext()) {
|
||||||
ClientContact remoteContact = remoteContactIterator.next();
|
try {
|
||||||
Optional<ClientContact> localContact = localContactIterator.next().get();
|
ClientContact remoteContact = remoteContactIterator.next();
|
||||||
|
Optional<ClientContact> localContact = localContactIterator.next().get();
|
||||||
|
|
||||||
remoteContact.setRelay(client.getPeerName());
|
remoteContact.setRelay(client.getPeerName());
|
||||||
|
|
||||||
if (!remoteContact.isInactive() && (!localContact.isPresent() || client.getPeerName().equals(localContact.get().getRelay()))) {
|
if (!remoteContact.isInactive() && (!localContact.isPresent() || client.getPeerName().equals(localContact.get().getRelay()))) {
|
||||||
contactsAdded++;
|
contactsAdded++;
|
||||||
directory.add(handle, remoteContact);
|
directory.add(handle, remoteContact);
|
||||||
} else {
|
} else {
|
||||||
if (localContact.isPresent() && client.getPeerName().equals(localContact.get().getRelay())) {
|
if (localContact.isPresent() && client.getPeerName().equals(localContact.get().getRelay())) {
|
||||||
contactsRemoved++;
|
contactsRemoved++;
|
||||||
directory.remove(handle, remoteContact.getToken());
|
directory.remove(handle, remoteContact.getToken());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("JSON Serialization Failed: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
org.whispersystems.textsecuregcm.metrics.JsonMetricsReporterFactory
|
||||||
@@ -2,19 +2,19 @@ package org.whispersystems.textsecuregcm.tests.websocket;
|
|||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
|
||||||
import org.whispersystems.textsecuregcm.entities.PendingMessage;
|
|
||||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
import org.whispersystems.textsecuregcm.storage.StoredMessages;
|
||||||
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
import org.whispersystems.textsecuregcm.websocket.ConnectListener;
|
import org.whispersystems.textsecuregcm.websocket.ConnectListener;
|
||||||
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
|
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
|
||||||
import org.whispersystems.textsecuregcm.websocket.WebSocketConnection;
|
import org.whispersystems.textsecuregcm.websocket.WebSocketConnection;
|
||||||
@@ -32,6 +32,7 @@ import io.dropwizard.auth.basic.BasicCredentials;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||||
|
|
||||||
public class WebSocketConnectionTest {
|
public class WebSocketConnectionTest {
|
||||||
|
|
||||||
@@ -112,13 +113,15 @@ public class WebSocketConnectionTest {
|
|||||||
public void testOpen() throws Exception {
|
public void testOpen() throws Exception {
|
||||||
StoredMessages storedMessages = mock(StoredMessages.class);
|
StoredMessages storedMessages = mock(StoredMessages.class);
|
||||||
|
|
||||||
List<PendingMessage> outgoingMessages = new LinkedList<PendingMessage>() {{
|
List<OutgoingMessageSignal> outgoingMessages = new LinkedList<OutgoingMessageSignal>() {{
|
||||||
add(new PendingMessage("sender1", 1111, false, "first"));
|
add(createMessage("sender1", 1111, false, "first"));
|
||||||
add(new PendingMessage("sender1", 2222, false, "second"));
|
add(createMessage("sender1", 2222, false, "second"));
|
||||||
add(new PendingMessage("sender2", 3333, false, "third"));
|
add(createMessage("sender2", 3333, false, "third"));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
when(device.getId()).thenReturn(2L);
|
when(device.getId()).thenReturn(2L);
|
||||||
|
when(device.getSignalingKey()).thenReturn(Base64.encodeBytes(new byte[52]));
|
||||||
|
|
||||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||||
when(account.getNumber()).thenReturn("+14152222222");
|
when(account.getNumber()).thenReturn("+14152222222");
|
||||||
|
|
||||||
@@ -167,16 +170,26 @@ public class WebSocketConnectionTest {
|
|||||||
futures.get(0).setException(new IOException());
|
futures.get(0).setException(new IOException());
|
||||||
futures.get(2).setException(new IOException());
|
futures.get(2).setException(new IOException());
|
||||||
|
|
||||||
List<PendingMessage> pending = new LinkedList<PendingMessage>() {{
|
List<OutgoingMessageSignal> pending = new LinkedList<OutgoingMessageSignal>() {{
|
||||||
add(new PendingMessage("sender1", 1111, false, "first"));
|
add(createMessage("sender1", 1111, false, "first"));
|
||||||
add(new PendingMessage("sender2", 3333, false, "third"));
|
add(createMessage("sender2", 3333, false, "third"));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
verify(pushSender, times(2)).sendMessage(eq(account), eq(device), any(PendingMessage.class));
|
verify(pushSender, times(2)).sendMessage(eq(account), eq(device), any(OutgoingMessageSignal.class));
|
||||||
verify(pushSender, times(1)).sendMessage(eq(sender1), eq(sender1device), any(MessageProtos.OutgoingMessageSignal.class));
|
verify(pushSender, times(1)).sendMessage(eq(sender1), eq(sender1device), any(OutgoingMessageSignal.class));
|
||||||
|
|
||||||
connection.onConnectionLost();
|
connection.onConnectionLost();
|
||||||
verify(pubSubManager).unsubscribe(eq(new WebsocketAddress("+14152222222", 2L)), eq(connection));
|
verify(pubSubManager).unsubscribe(eq(new WebsocketAddress("+14152222222", 2L)), eq(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OutgoingMessageSignal createMessage(String sender, long timestamp, boolean receipt, String content) {
|
||||||
|
return OutgoingMessageSignal.newBuilder()
|
||||||
|
.setSource(sender)
|
||||||
|
.setSourceDevice(1)
|
||||||
|
.setType(receipt ? OutgoingMessageSignal.Type.RECEIPT_VALUE : OutgoingMessageSignal.Type.CIPHERTEXT_VALUE)
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.setMessage(ByteString.copyFrom(content.getBytes()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user