diff --git a/gcm-sender-async/pom.xml b/gcm-sender-async/pom.xml deleted file mode 100644 index dcf32b649..000000000 --- a/gcm-sender-async/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - TextSecureServer - org.whispersystems.textsecure - JGITVER - - 4.0.0 - gcm-sender-async - - - - io.github.resilience4j - resilience4j-retry - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - com.google.guava - guava - - - org.apache.httpcomponents - httpclient - test - - - org.slf4j - jcl-over-slf4j - test - - - org.slf4j - slf4j-nop - test - - - - diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/AuthenticationFailedException.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/AuthenticationFailedException.java deleted file mode 100644 index 986cc23a7..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/AuthenticationFailedException.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - - -public class AuthenticationFailedException extends Exception { -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/InvalidRequestException.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/InvalidRequestException.java deleted file mode 100644 index e9ea47013..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/InvalidRequestException.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - - -public class InvalidRequestException extends Exception { -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Message.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Message.java deleted file mode 100644 index 038639142..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Message.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.whispersystems.gcm.server.internal.GcmRequestEntity; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class Message { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - private final String collapseKey; - private final Long ttl; - private final Boolean delayWhileIdle; - private final Map data; - private final List registrationIds; - private final String priority; - - private Message(String collapseKey, Long ttl, Boolean delayWhileIdle, - Map data, List registrationIds, - String priority) - { - this.collapseKey = collapseKey; - this.ttl = ttl; - this.delayWhileIdle = delayWhileIdle; - this.data = data; - this.registrationIds = registrationIds; - this.priority = priority; - } - - public String serialize() throws JsonProcessingException { - GcmRequestEntity requestEntity = new GcmRequestEntity(collapseKey, ttl, delayWhileIdle, - data, registrationIds, priority); - - return objectMapper.writeValueAsString(requestEntity); - } - - /** - * Construct a new Message using a Builder. - * @return A new Builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - - private String collapseKey = null; - private Long ttl = null; - private Boolean delayWhileIdle = null; - private Map data = null; - private List registrationIds = new LinkedList<>(); - private String priority = null; - - private Builder() {} - - /** - * @param collapseKey The GCM collapse key to use (optional). - * @return The Builder. - */ - public Builder withCollapseKey(String collapseKey) { - this.collapseKey = collapseKey; - return this; - } - - /** - * @param seconds The TTL (in seconds) for this message (optional). - * @return The Builder. - */ - public Builder withTtl(long seconds) { - this.ttl = seconds; - return this; - } - - /** - * @param delayWhileIdle Set GCM delay_while_idle (optional). - * @return The Builder. - */ - public Builder withDelayWhileIdle(boolean delayWhileIdle) { - this.delayWhileIdle = delayWhileIdle; - return this; - } - - /** - * Set a key in the GCM JSON payload delivered to the application (optional). - * @param key The key to set. - * @param value The value to set. - * @return The Builder. - */ - public Builder withDataPart(String key, String value) { - if (data == null) { - data = new HashMap<>(); - } - data.put(key, value); - return this; - } - - /** - * Set the destination GCM registration ID (mandatory). - * @param registrationId The destination GCM registration ID. - * @return The Builder. - */ - public Builder withDestination(String registrationId) { - this.registrationIds.clear(); - this.registrationIds.add(registrationId); - return this; - } - - /** - * Set the GCM message priority (optional). - * - * @param priority Valid values are "normal" and "high." - * On iOS, these correspond to APNs priority 5 and 10. - * @return The Builder. - */ - public Builder withPriority(String priority) { - this.priority = priority; - return this; - } - - /** - * Construct a message object. - * - * @return An immutable message object, as configured by this builder. - */ - public Message build() { - if (registrationIds.isEmpty()) { - throw new IllegalArgumentException("You must specify a destination!"); - } - - return new Message(collapseKey, ttl, delayWhileIdle, data, registrationIds, priority); - } - } - - -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Result.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Result.java deleted file mode 100644 index b659c3d1b..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Result.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -/** - * The result of a GCM send operation. - */ -public class Result { - - private final String canonicalRegistrationId; - private final String messageId; - private final String error; - - Result(String canonicalRegistrationId, String messageId, String error) { - this.canonicalRegistrationId = canonicalRegistrationId; - this.messageId = messageId; - this.error = error; - } - - /** - * Returns the "canonical" GCM registration ID for this destination. - * See GCM documentation for details. - * @return The canonical GCM registration ID. - */ - public String getCanonicalRegistrationId() { - return canonicalRegistrationId; - } - - /** - * @return If a "canonical" GCM registration ID is present in the response. - */ - public boolean hasCanonicalRegistrationId() { - return canonicalRegistrationId != null && !canonicalRegistrationId.isEmpty(); - } - - /** - * @return The assigned GCM message ID, if successful. - */ - public String getMessageId() { - return messageId; - } - - /** - * @return The raw error string, if present. - */ - public String getError() { - return error; - } - - /** - * @return If the send was a success. - */ - public boolean isSuccess() { - return messageId != null && !messageId.isEmpty() && (error == null || error.isEmpty()); - } - - /** - * @return If the destination GCM registration ID is no longer registered. - */ - public boolean isUnregistered() { - return "NotRegistered".equals(error); - } - - /** - * @return If messages to this device are being throttled. - */ - public boolean isThrottled() { - return "DeviceMessageRateExceeded".equals(error); - } - - /** - * @return If the destination GCM registration ID is invalid. - */ - public boolean isInvalidRegistrationId() { - return "InvalidRegistration".equals(error); - } - -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Sender.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Sender.java deleted file mode 100644 index 84fc57769..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/Sender.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; -import io.github.resilience4j.core.IntervalFunction; -import io.github.resilience4j.retry.Retry; -import io.github.resilience4j.retry.RetryConfig; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse.BodyHandlers; -import java.security.SecureRandom; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeoutException; -import org.whispersystems.gcm.server.internal.GcmResponseEntity; -import org.whispersystems.gcm.server.internal.GcmResponseListEntity; - -/** - * The main interface to sending GCM messages. Thread safe. - * - * @author Moxie Marlinspike - */ -public class Sender { - - private static final String PRODUCTION_URL = "https://fcm.googleapis.com/fcm/send"; - - private final String authorizationHeader; - private final URI uri; - private final Retry retry; - private final ObjectMapper mapper; - private final ScheduledExecutorService executorService; - - private final HttpClient[] clients = new HttpClient[10]; - private final SecureRandom random = new SecureRandom(); - - /** - * Construct a Sender instance. - * - * @param apiKey Your application's GCM API key. - */ - public Sender(String apiKey, ObjectMapper mapper) { - this(apiKey, mapper, 10); - } - - /** - * Construct a Sender instance with a specified retry count. - * - * @param apiKey Your application's GCM API key. - * @param retryCount The number of retries to attempt on a network error or 500 response. - */ - public Sender(String apiKey, ObjectMapper mapper, int retryCount) { - this(apiKey, mapper, retryCount, PRODUCTION_URL); - } - - @VisibleForTesting - public Sender(String apiKey, ObjectMapper mapper, int retryCount, String url) { - this.mapper = mapper; - this.executorService = Executors.newSingleThreadScheduledExecutor(); - this.uri = URI.create(url); - this.authorizationHeader = String.format("key=%s", apiKey); - this.retry = Retry.of("fcm-sender", RetryConfig.custom() - .maxAttempts(retryCount) - .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(Duration.ofMillis(100), 2.0)) - .retryOnException(this::isRetryableException) - .build()); - - for (int i=0;i send(Message message) { - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(uri) - .header("Authorization", authorizationHeader) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(message.serialize())) - .timeout(Duration.ofSeconds(10)) - .build(); - - return retry.executeCompletionStage(executorService, - () -> getClient().sendAsync(request, BodyHandlers.ofByteArray()) - .thenApply(response -> { - switch (response.statusCode()) { - case 400: throw new CompletionException(new InvalidRequestException()); - case 401: throw new CompletionException(new AuthenticationFailedException()); - case 204: - case 200: return response.body(); - default: throw new CompletionException(new ServerFailedException("Bad status: " + response.statusCode())); - } - }) - .thenApply(responseBytes -> { - try { - List responseList = mapper.readValue(responseBytes, GcmResponseListEntity.class).getResults(); - - if (responseList == null || responseList.size() == 0) { - throw new CompletionException(new IOException("Empty response list!")); - } - - GcmResponseEntity responseEntity = responseList.get(0); - - return new Result(responseEntity.getCanonicalRegistrationId(), - responseEntity.getMessageId(), - responseEntity.getError()); - } catch (IOException e) { - throw new CompletionException(e); - } - })).toCompletableFuture(); - } catch (JsonProcessingException e) { - return CompletableFuture.failedFuture(e); - } - } - - public Retry getRetry() { - return retry; - } - - private HttpClient getClient() { - return clients[random.nextInt(clients.length)]; - } - -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/ServerFailedException.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/ServerFailedException.java deleted file mode 100644 index 611512109..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/ServerFailedException.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -public class ServerFailedException extends Exception { - public ServerFailedException(String message) { - super(message); - } - - public ServerFailedException(Exception e) { - super(e); - } -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmRequestEntity.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmRequestEntity.java deleted file mode 100644 index 980c8184d..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmRequestEntity.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server.internal; - - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; -import java.util.Map; - -@JsonInclude(JsonInclude.Include.NON_NULL) -public class GcmRequestEntity { - - @JsonProperty(value = "collapse_key") - private String collapseKey; - - @JsonProperty(value = "time_to_live") - private Long ttl; - - @JsonProperty(value = "delay_while_idle") - private Boolean delayWhileIdle; - - @JsonProperty(value = "data") - private Map data; - - @JsonProperty(value = "registration_ids") - private List registrationIds; - - @JsonProperty - private String priority; - - public GcmRequestEntity(String collapseKey, Long ttl, Boolean delayWhileIdle, - Map data, List registrationIds, - String priority) - { - this.collapseKey = collapseKey; - this.ttl = ttl; - this.delayWhileIdle = delayWhileIdle; - this.data = data; - this.registrationIds = registrationIds; - this.priority = priority; - } -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseEntity.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseEntity.java deleted file mode 100644 index 7683111cd..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseEntity.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server.internal; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class GcmResponseEntity { - - @JsonProperty(value = "message_id") - private String messageId; - - @JsonProperty(value = "registration_id") - private String canonicalRegistrationId; - - @JsonProperty - private String error; - - public String getMessageId() { - return messageId; - } - - public String getCanonicalRegistrationId() { - return canonicalRegistrationId; - } - - public String getError() { - return error; - } -} diff --git a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseListEntity.java b/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseListEntity.java deleted file mode 100644 index 1005f2116..000000000 --- a/gcm-sender-async/src/main/java/org/whispersystems/gcm/server/internal/GcmResponseListEntity.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server.internal; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class GcmResponseListEntity { - - @JsonProperty - private List results; - - public List getResults() { - return results; - } -} diff --git a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/MessageTest.java b/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/MessageTest.java deleted file mode 100644 index e62e000a9..000000000 --- a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/MessageTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture; - -import java.io.IOException; -import org.junit.jupiter.api.Test; - -public class MessageTest { - - @Test - void testMinimal() throws IOException { - Message message = Message.newBuilder() - .withDestination("1") - .build(); - - assertEquals(message.serialize(), jsonFixture("fixtures/message-minimal.json")); - } - - @Test - void testComplete() throws IOException { - Message message = Message.newBuilder() - .withDestination("1") - .withCollapseKey("collapse") - .withDelayWhileIdle(true) - .withTtl(10) - .withPriority("high") - .build(); - - assertEquals(message.serialize(), jsonFixture("fixtures/message-complete.json")); - } - - @Test - void testWithData() throws IOException { - Message message = Message.newBuilder() - .withDestination("2") - .withDataPart("key1", "value1") - .withDataPart("key2", "value2") - .build(); - - assertEquals(message.serialize(), jsonFixture("fixtures/message-data.json")); - } - -} diff --git a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SenderTest.java b/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SenderTest.java deleted file mode 100644 index e2d9dcd1f..000000000 --- a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SenderTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture; -import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.client.CountMatchingStrategy; -import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -class SenderTest { - - @RegisterExtension - private final WireMockExtension wireMock = WireMockExtension.newInstance() - .options(wireMockConfig().dynamicPort().dynamicHttpsPort()) - .build(); - - private static final ObjectMapper mapper = new ObjectMapper(); - - static { - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } - - @Test - void testSuccess() throws InterruptedException, ExecutionException, TimeoutException, IOException { - wireMock.stubFor(any(anyUrl()) - .willReturn(aResponse() - .withStatus(200) - .withBody(fixture("fixtures/response-success.json")))); - - - Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - Result result = future.get(10, TimeUnit.SECONDS); - - assertTrue(result.isSuccess()); - assertFalse(result.isThrottled()); - assertFalse(result.isUnregistered()); - assertEquals(result.getMessageId(), "1:08"); - assertNull(result.getError()); - assertNull(result.getCanonicalRegistrationId()); - - wireMock.verify(1, postRequestedFor(urlEqualTo("/gcm/send")) - .withHeader("Authorization", equalTo("key=foobarbaz")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalTo(jsonFixture("fixtures/message-minimal.json")))); - } - - @Test - void testBadApiKey() throws InterruptedException, TimeoutException { - wireMock.stubFor(any(anyUrl()) - .willReturn(aResponse() - .withStatus(401))); - - Sender sender = new Sender("foobar", mapper, 10, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - try { - future.get(10, TimeUnit.SECONDS); - throw new AssertionError(); - } catch (ExecutionException ee) { - assertTrue(ee.getCause() instanceof AuthenticationFailedException); - } - - wireMock.verify(1, anyRequestedFor(anyUrl())); - } - - @Test - void testBadRequest() throws TimeoutException, InterruptedException { - wireMock.stubFor(any(anyUrl()) - .willReturn(aResponse() - .withStatus(400))); - - Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - try { - future.get(10, TimeUnit.SECONDS); - throw new AssertionError(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof InvalidRequestException); - } - - wireMock.verify(1, anyRequestedFor(anyUrl())); - } - - @Test - void testServerError() throws TimeoutException, InterruptedException { - wireMock.stubFor(any(anyUrl()) - .willReturn(aResponse() - .withStatus(503))); - - Sender sender = new Sender("foobarbaz", mapper, 3, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - try { - future.get(10, TimeUnit.SECONDS); - throw new AssertionError(); - } catch (ExecutionException ee) { - assertTrue(ee.getCause() instanceof ServerFailedException); - } - - wireMock.verify(3, anyRequestedFor(anyUrl())); - } - - @Test - void testServerErrorRecovery() throws InterruptedException, ExecutionException, TimeoutException { - - wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(503))); - - Sender sender = new Sender("foobarbaz", mapper, 4, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - // up to three failures can happen, with 100ms exponential backoff - // if we end up using the fourth, and final try, it would be after ~700 ms - CompletableFuture.delayedExecutor(300, TimeUnit.MILLISECONDS).execute(() -> - wireMock.stubFor(any(anyUrl()) - .willReturn(aResponse() - .withStatus(200) - .withBody(fixture("fixtures/response-success.json")))) - ); - - Result result = future.get(10, TimeUnit.SECONDS); - - wireMock.verify(new CountMatchingStrategy(CountMatchingStrategy.GREATER_THAN, 1), anyRequestedFor(anyUrl())); - assertTrue(result.isSuccess()); - assertFalse(result.isThrottled()); - assertFalse(result.isUnregistered()); - assertEquals(result.getMessageId(), "1:08"); - assertNull(result.getError()); - assertNull(result.getCanonicalRegistrationId()); - } - - @Test - void testNetworkError() throws TimeoutException, InterruptedException { - - wireMock.stubFor(any(anyUrl()) - .willReturn(ok())); - - Sender sender = new Sender("foobarbaz", mapper ,2, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - - wireMock.getRuntimeInfo().getWireMock().shutdown(); - - CompletableFuture future = sender.send(Message.newBuilder().withDestination("1").build()); - - try { - future.get(10, TimeUnit.SECONDS); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof IOException); - } - } - - @Test - void testNotRegistered() throws InterruptedException, ExecutionException, TimeoutException { - - wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200) - .withBody(fixture("fixtures/response-not-registered.json")))); - - Sender sender = new Sender("foobarbaz", mapper,2, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - CompletableFuture future = sender.send(Message.newBuilder() - .withDestination("2") - .withDataPart("message", "new message!") - .build()); - - Result result = future.get(10, TimeUnit.SECONDS); - - assertFalse(result.isSuccess()); - assertTrue(result.isUnregistered()); - assertFalse(result.isThrottled()); - assertEquals(result.getError(), "NotRegistered"); - } -} diff --git a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SimultaneousSenderTest.java b/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SimultaneousSenderTest.java deleted file mode 100644 index a5fcf958e..000000000 --- a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/SimultaneousSenderTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -class SimultaneousSenderTest { - - @RegisterExtension - private final WireMockExtension wireMock = WireMockExtension.newInstance() - .options(wireMockConfig().dynamicPort().dynamicHttpsPort()) - .build(); - - private static final ObjectMapper mapper = new ObjectMapper(); - - static { - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } - - @Test - void testSimultaneousSuccess() throws TimeoutException, InterruptedException, ExecutionException { - wireMock.stubFor(post(urlPathEqualTo("/gcm/send")) - .willReturn(aResponse() - .withStatus(200) - .withBody(fixture("fixtures/response-success.json")))); - - Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - List> results = new LinkedList<>(); - - for (int i=0;i<1000;i++) { - results.add(sender.send(Message.newBuilder().withDestination("1").build())); - } - - for (CompletableFuture future : results) { - Result result = future.get(60, TimeUnit.SECONDS); - - if (!result.isSuccess()) { - throw new AssertionError(result.getError()); - } - } - } - - @Test - @Disabled - void testSimultaneousFailure() { - wireMock.stubFor(post(urlPathEqualTo("/gcm/send")) - .willReturn(aResponse() - .withStatus(503))); - - Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.getPort() + "/gcm/send"); - List> futures = new LinkedList<>(); - - for (int i=0;i<1000;i++) { - futures.add(sender.send(Message.newBuilder().withDestination("1").build())); - } - - for (CompletableFuture future : futures) { - final ExecutionException e = assertThrows(ExecutionException.class, () -> future.get(60, TimeUnit.SECONDS)); - - assertTrue(e.getCause() instanceof ServerFailedException, e.getCause().toString()); - } - } -} diff --git a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/FixtureHelpers.java b/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/FixtureHelpers.java deleted file mode 100644 index 994beae62..000000000 --- a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/FixtureHelpers.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server.util; - -import com.google.common.base.Charsets; -import com.google.common.io.Resources; - -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * A set of helper method for fixture files. - */ -public class FixtureHelpers { - private FixtureHelpers() { /* singleton */ } - - /** - * Reads the given fixture file from the classpath (e. g. {@code src/test/resources}) - * and returns its contents as a UTF-8 string. - * - * @param filename the filename of the fixture file - * @return the contents of {@code src/test/resources/{filename}} - * @throws IllegalArgumentException if an I/O error occurs. - */ - public static String fixture(String filename) { - return fixture(filename, Charsets.UTF_8); - } - - /** - * Reads the given fixture file from the classpath (e. g. {@code src/test/resources}) - * and returns its contents as a string. - * - * @param filename the filename of the fixture file - * @param charset the character set of {@code filename} - * @return the contents of {@code src/test/resources/{filename}} - * @throws IllegalArgumentException if an I/O error occurs. - */ - private static String fixture(String filename, Charset charset) { - try { - return Resources.toString(Resources.getResource(filename), charset).trim(); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } - } -} diff --git a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/JsonHelpers.java b/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/JsonHelpers.java deleted file mode 100644 index e71cec4bf..000000000 --- a/gcm-sender-async/src/test/java/org/whispersystems/gcm/server/util/JsonHelpers.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.gcm.server.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; - -import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture; - -public class JsonHelpers { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - public static String asJson(Object object) throws JsonProcessingException { - return objectMapper.writeValueAsString(object); - } - - public static T fromJson(String value, Class clazz) throws IOException { - return objectMapper.readValue(value, clazz); - } - - public static String jsonFixture(String filename) throws IOException { - return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class)); - } -} diff --git a/gcm-sender-async/src/test/resources/fixtures/message-complete.json b/gcm-sender-async/src/test/resources/fixtures/message-complete.json deleted file mode 100644 index 43a4dcf06..000000000 --- a/gcm-sender-async/src/test/resources/fixtures/message-complete.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "priority" : "high", - "collapse_key" : "collapse", - "time_to_live" : 10, - "delay_while_idle" : true, - "registration_ids" : ["1"] -} \ No newline at end of file diff --git a/gcm-sender-async/src/test/resources/fixtures/message-data.json b/gcm-sender-async/src/test/resources/fixtures/message-data.json deleted file mode 100644 index 4993e04e4..000000000 --- a/gcm-sender-async/src/test/resources/fixtures/message-data.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "data" : { - "key1" : "value1", - "key2" : "value2" - }, - "registration_ids" : ["2"] -} \ No newline at end of file diff --git a/gcm-sender-async/src/test/resources/fixtures/message-minimal.json b/gcm-sender-async/src/test/resources/fixtures/message-minimal.json deleted file mode 100644 index 2aab43b01..000000000 --- a/gcm-sender-async/src/test/resources/fixtures/message-minimal.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "registration_ids" : ["1"] -} \ No newline at end of file diff --git a/gcm-sender-async/src/test/resources/fixtures/response-not-registered.json b/gcm-sender-async/src/test/resources/fixtures/response-not-registered.json deleted file mode 100644 index 9363c9d72..000000000 --- a/gcm-sender-async/src/test/resources/fixtures/response-not-registered.json +++ /dev/null @@ -1,8 +0,0 @@ -{ "multicast_id": 216, - "success": 0, - "failure": 1, - "canonical_ids": 0, - "results": [ - { "error": "NotRegistered"} - ] -} \ No newline at end of file diff --git a/gcm-sender-async/src/test/resources/fixtures/response-success.json b/gcm-sender-async/src/test/resources/fixtures/response-success.json deleted file mode 100644 index 7ae2b3d96..000000000 --- a/gcm-sender-async/src/test/resources/fixtures/response-success.json +++ /dev/null @@ -1,8 +0,0 @@ -{ "multicast_id": 108, - "success": 1, - "failure": 0, - "canonical_ids": 0, - "results": [ - { "message_id": "1:08" } - ] -} \ No newline at end of file diff --git a/gcm-sender-async/src/test/resources/logback-test.xml b/gcm-sender-async/src/test/resources/logback-test.xml deleted file mode 100644 index 2b693894a..000000000 --- a/gcm-sender-async/src/test/resources/logback-test.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/pom.xml b/pom.xml index 094277a19..8e2db41e2 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ redis-dispatch websocket-resources - gcm-sender-async service diff --git a/service/pom.xml b/service/pom.xml index 2f6561cb6..879cf92b9 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -34,11 +34,6 @@ websocket-resources ${project.version} - - org.whispersystems.textsecure - gcm-sender-async - ${project.version} - org.signal libsignal-server