mirror of
https://github.com/signalapp/Signal-Server.git
synced 2025-12-13 01:50:34 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aea3f299a0 | ||
|
|
5667476780 | ||
|
|
b263f47826 | ||
|
|
21723d6313 | ||
|
|
a63cdc76b0 | ||
|
|
129e372613 |
2
pom.xml
2
pom.xml
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<version>0.4</version>
|
||||
<version>0.8</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
||||
@@ -20,11 +20,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.yammer.dropwizard.config.Configuration;
|
||||
import com.yammer.dropwizard.db.DatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DataDogConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GraphiteConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MemcacheConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MetricsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
@@ -87,7 +87,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DataDogConfiguration datadog = new DataDogConfiguration();
|
||||
private MetricsConfiguration metrics = new MetricsConfiguration();
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
@@ -141,7 +141,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return graphite;
|
||||
}
|
||||
|
||||
public DataDogConfiguration getDataDogConfiguration() {
|
||||
return datadog;
|
||||
public MetricsConfiguration getMetricsConfiguration() {
|
||||
return metrics;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.yammer.dropwizard.config.HttpConfiguration;
|
||||
import com.yammer.dropwizard.db.DatabaseConfiguration;
|
||||
import com.yammer.dropwizard.jdbi.DBIFactory;
|
||||
import com.yammer.dropwizard.migrations.MigrationsBundle;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Clock;
|
||||
import com.yammer.metrics.core.MetricPredicate;
|
||||
import com.yammer.metrics.reporting.DatadogReporter;
|
||||
@@ -48,6 +49,7 @@ import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.metrics.JsonMetricsReporter;
|
||||
import org.whispersystems.textsecuregcm.providers.MemcacheHealthCheck;
|
||||
import org.whispersystems.textsecuregcm.providers.MemcachedClientFactory;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||
@@ -168,11 +170,11 @@ public class WhisperServerService extends Service<WhisperServerConfiguration> {
|
||||
config.getGraphiteConfiguration().getPort());
|
||||
}
|
||||
|
||||
if (config.getDataDogConfiguration().isEnabled()) {
|
||||
new DatadogReporter.Builder().withApiKey(config.getDataDogConfiguration().getApiKey())
|
||||
.withVmMetricsEnabled(true)
|
||||
.build()
|
||||
.start(15, TimeUnit.SECONDS);
|
||||
if (config.getMetricsConfiguration().isEnabled()) {
|
||||
new JsonMetricsReporter("textsecure", Metrics.defaultRegistry(),
|
||||
config.getMetricsConfiguration().getToken(),
|
||||
config.getMetricsConfiguration().getHost())
|
||||
.start(60, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,10 @@ public class AccountController {
|
||||
throw new WebApplicationException(Response.status(403).build());
|
||||
}
|
||||
|
||||
if (accounts.isRelayListed(number)) {
|
||||
throw new WebApplicationException(Response.status(417).build());
|
||||
}
|
||||
|
||||
Device device = new Device();
|
||||
device.setId(Device.MASTER_ID);
|
||||
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
|
||||
|
||||
@@ -76,7 +76,7 @@ public class FederationController {
|
||||
@PathParam("attachmentId") long attachmentId)
|
||||
throws IOException
|
||||
{
|
||||
return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", peer.getName()),
|
||||
return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
attachmentId, Optional.<String>absent());
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class FederationController {
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return keysController.get(new NonLimitedAccount("Unknown", peer.getName()), number, Optional.<String>absent());
|
||||
return keysController.get(new NonLimitedAccount("Unknown", -1, peer.getName()), number, Optional.<String>absent());
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.warn("Rate limiting on federated channel", e);
|
||||
throw new IOException(e);
|
||||
@@ -106,7 +106,7 @@ public class FederationController {
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
return keysController.getDeviceKey(new NonLimitedAccount("Unknown", peer.getName()),
|
||||
return keysController.getDeviceKey(new NonLimitedAccount("Unknown", -1, peer.getName()),
|
||||
number, device, Optional.<String>absent());
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.warn("Rate limiting on federated channel", e);
|
||||
@@ -116,16 +116,17 @@ public class FederationController {
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/messages/{source}/{destination}")
|
||||
public void sendMessages(@Auth FederatedPeer peer,
|
||||
@PathParam("source") String source,
|
||||
@PathParam("destination") String destination,
|
||||
@Valid IncomingMessageList messages)
|
||||
@Path("/messages/{source}/{sourceDeviceId}/{destination}")
|
||||
public void sendMessages(@Auth FederatedPeer peer,
|
||||
@PathParam("source") String source,
|
||||
@PathParam("sourceDeviceId") long sourceDeviceId,
|
||||
@PathParam("destination") String destination,
|
||||
@Valid IncomingMessageList messages)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
messages.setRelay(null);
|
||||
messageController.sendMessage(new NonLimitedAccount(source, peer.getName()), destination, messages);
|
||||
messageController.sendMessage(new NonLimitedAccount(source, sourceDeviceId, peer.getName()), destination, messages);
|
||||
} catch (RateLimitExceededException e) {
|
||||
logger.warn("Rate limiting on federated channel", e);
|
||||
throw new IOException(e);
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
@@ -73,6 +74,20 @@ public class KeysController {
|
||||
keys.store(account.getNumber(), device.getId(), preKeys.getKeys(), preKeys.getLastResortKey());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKeyStatus getStatus(@Auth Account account) {
|
||||
int count = keys.getCount(account.getNumber(), account.getAuthenticatedDevice().get().getId());
|
||||
|
||||
if (count > 0) {
|
||||
count = count - 1;
|
||||
}
|
||||
|
||||
return new PreKeyStatus(count);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}/{device_id}")
|
||||
|
||||
@@ -186,7 +186,8 @@ public class MessageController {
|
||||
{
|
||||
try {
|
||||
FederatedClient client = federatedClientManager.getClient(messages.getRelay());
|
||||
client.sendMessages(source.getNumber(), destinationName, messages);
|
||||
client.sendMessages(source.getNumber(), source.getAuthenticatedDevice().get().getId(),
|
||||
destinationName, messages);
|
||||
} catch (NoSuchPeerException e) {
|
||||
throw new NoSuchUserException(e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class PreKeyStatus {
|
||||
|
||||
@JsonProperty
|
||||
private int count;
|
||||
|
||||
public PreKeyStatus(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public PreKeyStatus() {}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class FederatedClient {
|
||||
|
||||
private static final String USER_COUNT_PATH = "/v1/federation/user_count";
|
||||
private static final String USER_TOKENS_PATH = "/v1/federation/user_tokens/%d";
|
||||
private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%s";
|
||||
private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%d/%s";
|
||||
private static final String PREKEY_PATH_DEVICE = "/v1/federation/key/%s/%s";
|
||||
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
|
||||
|
||||
@@ -155,11 +155,11 @@ public class FederatedClient {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessages(String source, String destination, IncomingMessageList messages)
|
||||
public void sendMessages(String source, long sourceDeviceId, String destination, IncomingMessageList messages)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(RELAY_MESSAGE_PATH, source, destination));
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(RELAY_MESSAGE_PATH, source, sourceDeviceId, destination));
|
||||
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.entity(messages)
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.whispersystems.textsecuregcm.federation;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.base.Optional;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
public class NonLimitedAccount extends Account {
|
||||
|
||||
@@ -13,20 +14,32 @@ public class NonLimitedAccount extends Account {
|
||||
@JsonIgnore
|
||||
private final String relay;
|
||||
|
||||
public NonLimitedAccount(String number, String relay) {
|
||||
this.number = number;
|
||||
this.relay = relay;
|
||||
@JsonIgnore
|
||||
private final long deviceId;
|
||||
|
||||
public NonLimitedAccount(String number, long deviceId, String relay) {
|
||||
this.number = number;
|
||||
this.deviceId = deviceId;
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRateLimited() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getRelay() {
|
||||
return Optional.of(relay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Device> getAuthenticatedDevice() {
|
||||
return Optional.of(new Device(deviceId, null, null, null, null, null, false, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
package org.whispersystems.textsecuregcm.metrics;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.yammer.metrics.core.Clock;
|
||||
import com.yammer.metrics.core.Counter;
|
||||
import com.yammer.metrics.core.Gauge;
|
||||
import com.yammer.metrics.core.Histogram;
|
||||
import com.yammer.metrics.core.Metered;
|
||||
import com.yammer.metrics.core.Metric;
|
||||
import com.yammer.metrics.core.MetricName;
|
||||
import com.yammer.metrics.core.MetricProcessor;
|
||||
import com.yammer.metrics.core.MetricsRegistry;
|
||||
import com.yammer.metrics.core.Sampling;
|
||||
import com.yammer.metrics.core.Summarizable;
|
||||
import com.yammer.metrics.core.Timer;
|
||||
import com.yammer.metrics.core.VirtualMachineMetrics;
|
||||
import com.yammer.metrics.reporting.AbstractPollingReporter;
|
||||
import com.yammer.metrics.stats.Snapshot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Adapted from MetricsServlet.
|
||||
*/
|
||||
public class JsonMetricsReporter extends AbstractPollingReporter implements MetricProcessor<JsonMetricsReporter.Context> {
|
||||
private final Clock clock = Clock.defaultClock();
|
||||
private final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance();
|
||||
private final String service;
|
||||
private final MetricsRegistry registry;
|
||||
private final JsonFactory factory = new JsonFactory();
|
||||
|
||||
private final String table;
|
||||
private final String sunnylabsHost;
|
||||
private final String host;
|
||||
|
||||
private final boolean includeVMMetrics;
|
||||
|
||||
public JsonMetricsReporter(String service, MetricsRegistry registry, String token, String sunnylabsHost) throws UnknownHostException {
|
||||
this(service, registry, token, sunnylabsHost, true);
|
||||
}
|
||||
|
||||
public JsonMetricsReporter(String service, MetricsRegistry registry, String token, String sunnylabsHost, boolean includeVMMetrics) throws UnknownHostException {
|
||||
super(registry, "jsonmetrics-reporter");
|
||||
this.service = service;
|
||||
this.registry = registry;
|
||||
this.table = token;
|
||||
this.sunnylabsHost = sunnylabsHost;
|
||||
this.host = InetAddress.getLocalHost().getHostName();
|
||||
this.includeVMMetrics = includeVMMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
URL http = new URL("https", sunnylabsHost, 443, "/report/metrics?t=" + table + "&h=" + host);
|
||||
System.out.println("Reporting started to: " + http);
|
||||
HttpURLConnection urlc = (HttpURLConnection) http.openConnection();
|
||||
urlc.setDoOutput(true);
|
||||
urlc.addRequestProperty("Content-Type", "application/json");
|
||||
OutputStream outputStream = urlc.getOutputStream();
|
||||
writeJson(outputStream);
|
||||
outputStream.close();
|
||||
System.out.println("Reporting complete: " + urlc.getResponseCode());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static final class Context {
|
||||
final boolean showFullSamples;
|
||||
final JsonGenerator json;
|
||||
|
||||
Context(JsonGenerator json, boolean showFullSamples) {
|
||||
this.json = json;
|
||||
this.showFullSamples = showFullSamples;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeJson(OutputStream out) throws IOException {
|
||||
final JsonGenerator json = factory.createGenerator(out, JsonEncoding.UTF8);
|
||||
json.writeStartObject();
|
||||
if (includeVMMetrics) {
|
||||
writeVmMetrics(json);
|
||||
}
|
||||
writeRegularMetrics(json, false);
|
||||
json.writeEndObject();
|
||||
json.close();
|
||||
}
|
||||
|
||||
private void writeVmMetrics(JsonGenerator json) throws IOException {
|
||||
json.writeFieldName(service);
|
||||
json.writeStartObject();
|
||||
json.writeFieldName("jvm");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeFieldName("vm");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeStringField("name", vm.name());
|
||||
json.writeStringField("version", vm.version());
|
||||
}
|
||||
json.writeEndObject();
|
||||
|
||||
json.writeFieldName("memory");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeNumberField("totalInit", vm.totalInit());
|
||||
json.writeNumberField("totalUsed", vm.totalUsed());
|
||||
json.writeNumberField("totalMax", vm.totalMax());
|
||||
json.writeNumberField("totalCommitted", vm.totalCommitted());
|
||||
|
||||
json.writeNumberField("heapInit", vm.heapInit());
|
||||
json.writeNumberField("heapUsed", vm.heapUsed());
|
||||
json.writeNumberField("heapMax", vm.heapMax());
|
||||
json.writeNumberField("heapCommitted", vm.heapCommitted());
|
||||
|
||||
json.writeNumberField("heap_usage", vm.heapUsage());
|
||||
json.writeNumberField("non_heap_usage", vm.nonHeapUsage());
|
||||
json.writeFieldName("memory_pool_usages");
|
||||
json.writeStartObject();
|
||||
{
|
||||
for (Map.Entry<String, Double> pool : vm.memoryPoolUsage().entrySet()) {
|
||||
json.writeNumberField(pool.getKey(), pool.getValue());
|
||||
}
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
json.writeEndObject();
|
||||
|
||||
final Map<String, VirtualMachineMetrics.BufferPoolStats> bufferPoolStats = vm.getBufferPoolStats();
|
||||
if (!bufferPoolStats.isEmpty()) {
|
||||
json.writeFieldName("buffers");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeFieldName("direct");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeNumberField("count", bufferPoolStats.get("direct").getCount());
|
||||
json.writeNumberField("memoryUsed", bufferPoolStats.get("direct").getMemoryUsed());
|
||||
json.writeNumberField("totalCapacity", bufferPoolStats.get("direct").getTotalCapacity());
|
||||
}
|
||||
json.writeEndObject();
|
||||
|
||||
json.writeFieldName("mapped");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeNumberField("count", bufferPoolStats.get("mapped").getCount());
|
||||
json.writeNumberField("memoryUsed", bufferPoolStats.get("mapped").getMemoryUsed());
|
||||
json.writeNumberField("totalCapacity", bufferPoolStats.get("mapped").getTotalCapacity());
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
|
||||
|
||||
json.writeNumberField("daemon_thread_count", vm.daemonThreadCount());
|
||||
json.writeNumberField("thread_count", vm.threadCount());
|
||||
json.writeNumberField("current_time", clock.time());
|
||||
json.writeNumberField("uptime", vm.uptime());
|
||||
json.writeNumberField("fd_usage", vm.fileDescriptorUsage());
|
||||
|
||||
json.writeFieldName("thread-states");
|
||||
json.writeStartObject();
|
||||
{
|
||||
for (Map.Entry<Thread.State, Double> entry : vm.threadStatePercentages()
|
||||
.entrySet()) {
|
||||
json.writeNumberField(entry.getKey().toString().toLowerCase(),
|
||||
entry.getValue());
|
||||
}
|
||||
}
|
||||
json.writeEndObject();
|
||||
|
||||
json.writeFieldName("garbage-collectors");
|
||||
json.writeStartObject();
|
||||
{
|
||||
for (Map.Entry<String, VirtualMachineMetrics.GarbageCollectorStats> entry : vm.garbageCollectors()
|
||||
.entrySet()) {
|
||||
json.writeFieldName(entry.getKey());
|
||||
json.writeStartObject();
|
||||
{
|
||||
final VirtualMachineMetrics.GarbageCollectorStats gc = entry.getValue();
|
||||
json.writeNumberField("runs", gc.getRuns());
|
||||
json.writeNumberField("time", gc.getTime(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
json.writeEndObject();
|
||||
json.writeEndObject();
|
||||
}
|
||||
|
||||
public void writeRegularMetrics(JsonGenerator json, boolean showFullSamples) throws IOException {
|
||||
for (Map.Entry<String, SortedMap<MetricName, Metric>> entry : registry.groupedMetrics().entrySet()) {
|
||||
for (Map.Entry<MetricName, Metric> subEntry : entry.getValue().entrySet()) {
|
||||
json.writeFieldName(sanitize(subEntry.getKey()));
|
||||
try {
|
||||
subEntry.getValue()
|
||||
.processWith(this,
|
||||
subEntry.getKey(),
|
||||
new Context(json, showFullSamples));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processHistogram(MetricName name, Histogram histogram, Context context) throws Exception {
|
||||
final JsonGenerator json = context.json;
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeNumberField("count", histogram.count());
|
||||
writeSummarizable(histogram, json);
|
||||
writeSampling(histogram, json);
|
||||
if (context.showFullSamples) {
|
||||
json.writeObjectField("values", histogram.getSnapshot().getValues());
|
||||
}
|
||||
histogram.clear();
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCounter(MetricName name, Counter counter, Context context) throws Exception {
|
||||
final JsonGenerator json = context.json;
|
||||
json.writeNumber(counter.count());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processGauge(MetricName name, Gauge<?> gauge, Context context) throws Exception {
|
||||
final JsonGenerator json = context.json;
|
||||
json.writeObject(evaluateGauge(gauge));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMeter(MetricName name, Metered meter, Context context) throws Exception {
|
||||
final JsonGenerator json = context.json;
|
||||
json.writeStartObject();
|
||||
{
|
||||
writeMeteredFields(meter, json);
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTimer(MetricName name, Timer timer, Context context) throws Exception {
|
||||
final JsonGenerator json = context.json;
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeFieldName("duration");
|
||||
json.writeStartObject();
|
||||
{
|
||||
json.writeStringField("unit", timer.durationUnit().toString().toLowerCase());
|
||||
writeSummarizable(timer, json);
|
||||
writeSampling(timer, json);
|
||||
if (context.showFullSamples) {
|
||||
json.writeObjectField("values", timer.getSnapshot().getValues());
|
||||
}
|
||||
}
|
||||
json.writeEndObject();
|
||||
|
||||
json.writeFieldName("rate");
|
||||
json.writeStartObject();
|
||||
{
|
||||
writeMeteredFields(timer, json);
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
json.writeEndObject();
|
||||
}
|
||||
|
||||
private static Object evaluateGauge(Gauge<?> gauge) {
|
||||
try {
|
||||
return gauge.value();
|
||||
} catch (RuntimeException e) {
|
||||
return "error reading gauge: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeSummarizable(Summarizable metric, JsonGenerator json) throws IOException {
|
||||
json.writeNumberField("min", metric.min());
|
||||
json.writeNumberField("max", metric.max());
|
||||
json.writeNumberField("mean", metric.mean());
|
||||
}
|
||||
|
||||
private static void writeSampling(Sampling metric, JsonGenerator json) throws IOException {
|
||||
final Snapshot snapshot = metric.getSnapshot();
|
||||
json.writeNumberField("median", snapshot.getMedian());
|
||||
json.writeNumberField("p75", snapshot.get75thPercentile());
|
||||
json.writeNumberField("p95", snapshot.get95thPercentile());
|
||||
json.writeNumberField("p99", snapshot.get99thPercentile());
|
||||
json.writeNumberField("p999", snapshot.get999thPercentile());
|
||||
}
|
||||
|
||||
private static void writeMeteredFields(Metered metered, JsonGenerator json) throws IOException {
|
||||
json.writeNumberField("count", metered.count());
|
||||
json.writeNumberField("mean", metered.meanRate());
|
||||
json.writeNumberField("m1", metered.oneMinuteRate());
|
||||
json.writeNumberField("m5", metered.fiveMinuteRate());
|
||||
json.writeNumberField("m15", metered.fifteenMinuteRate());
|
||||
}
|
||||
|
||||
private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]");
|
||||
|
||||
private String sanitize(MetricName metricName) {
|
||||
return SIMPLE_NAMES.matcher(metricName.getGroup() + "." + metricName.getName()).replaceAll("_");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,13 @@ public class AccountsManager {
|
||||
else return Optional.absent();
|
||||
}
|
||||
|
||||
public boolean isRelayListed(String number) {
|
||||
byte[] token = Util.getContactToken(number);
|
||||
Optional<ClientContact> contact = directory.get(token);
|
||||
|
||||
return contact.isPresent() && !Util.isEmpty(contact.get().getRelay());
|
||||
}
|
||||
|
||||
private void updateDirectory(Account account) {
|
||||
if (account.isActive()) {
|
||||
byte[] token = Util.getContactToken(account.getNumber());
|
||||
|
||||
@@ -40,7 +40,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Keys {
|
||||
@@ -67,6 +66,9 @@ public abstract class Keys {
|
||||
@Mapper(PreKeyMapper.class)
|
||||
abstract List<PreKey> retrieveFirst(@Bind("number") String number);
|
||||
|
||||
@SqlQuery("SELECT COUNT(*) FROM keys WHERE number = :number AND device_id = :device_id")
|
||||
public abstract int getCount(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public void store(String number, long deviceId, List<PreKey> keys, PreKey lastResortKey) {
|
||||
for (PreKey key : keys) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Optional;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.yammer.dropwizard.testing.ResourceTest;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.FederationController;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.yammer.dropwizard.testing.JsonHelpers.jsonFixture;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class FederatedControllerTest extends ResourceTest {
|
||||
|
||||
private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111";
|
||||
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
|
||||
|
||||
private PushSender pushSender = mock(PushSender.class );
|
||||
private FederatedClientManager federatedClientManager = mock(FederatedClientManager.class);
|
||||
private AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
protected void setUpResources() throws Exception {
|
||||
addProvider(AuthHelper.getAuthenticator());
|
||||
|
||||
List<Device> singleDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111));
|
||||
}};
|
||||
|
||||
List<Device> multiDeviceList = new LinkedList<Device>() {{
|
||||
add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 222));
|
||||
add(new Device(2, "foo", "bar", "baz", "isgcm", null, false, 333));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, false, singleDeviceList);
|
||||
Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, false, multiDeviceList);
|
||||
|
||||
when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount));
|
||||
when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount));
|
||||
|
||||
when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager);
|
||||
addResource(new FederationController(accountsManager, null, null, messageController));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleDeviceCurrent() throws Exception {
|
||||
ClientResponse response =
|
||||
client().resource(String.format("/v1/federation/messages/+14152223333/1/%s", SINGLE_DEVICE_RECIPIENT))
|
||||
.header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo"))
|
||||
.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class))
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.put(ClientResponse.class);
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(204)));
|
||||
|
||||
verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.yammer.dropwizard.testing.ResourceTest;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyStatus;
|
||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
@@ -71,11 +72,25 @@ public class KeyControllerTest extends ResourceTest {
|
||||
allKeys.add(SAMPLE_KEY);
|
||||
allKeys.add(SAMPLE_KEY2);
|
||||
allKeys.add(SAMPLE_KEY3);
|
||||
|
||||
when(keys.get(EXISTS_NUMBER)).thenReturn(Optional.of(new UnstructuredPreKeyList(allKeys)));
|
||||
when(keys.getCount(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(5);
|
||||
|
||||
addResource(new KeysController(rateLimiters, keys, accounts, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validKeyStatusTest() throws Exception {
|
||||
PreKeyStatus result = client().resource("/v1/keys")
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyStatus.class);
|
||||
|
||||
assertThat(result.getCount() == 4);
|
||||
|
||||
verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validLegacyRequestTest() throws Exception {
|
||||
PreKey result = client().resource(String.format("/v1/keys/%s", EXISTS_NUMBER))
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -40,7 +42,14 @@ public class AuthHelper {
|
||||
when(account.getRelay()).thenReturn(Optional.<String>absent());
|
||||
when(accounts.get(VALID_NUMBER)).thenReturn(Optional.of(account));
|
||||
|
||||
return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(new FederationConfiguration()),
|
||||
List<FederatedPeer> peer = new LinkedList<FederatedPeer>() {{
|
||||
add(new FederatedPeer("cyanogen", "https://foo", "foofoo", "bazzzzz"));
|
||||
}};
|
||||
|
||||
FederationConfiguration federationConfiguration = mock(FederationConfiguration.class);
|
||||
when(federationConfiguration.getPeers()).thenReturn(peer);
|
||||
|
||||
return new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(federationConfiguration),
|
||||
FederatedPeer.class,
|
||||
new AccountAuthenticator(accounts),
|
||||
Account.class, "WhisperServer");
|
||||
|
||||
Reference in New Issue
Block a user