mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 15:38:05 +01:00
Implement key transparency endpoints using simple-grpc
This commit is contained in:
@@ -554,8 +554,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
.maxThreads(2)
|
||||
.minThreads(2)
|
||||
.build();
|
||||
ExecutorService keyTransparencyCallbackExecutor = environment.lifecycle()
|
||||
.virtualExecutorService(name(getClass(), "keyTransparency-%d"));
|
||||
ExecutorService googlePlayBillingExecutor = environment.lifecycle()
|
||||
.virtualExecutorService(name(getClass(), "googlePlayBilling-%d"));
|
||||
ExecutorService appleAppStoreExecutor = environment.lifecycle()
|
||||
@@ -606,8 +604,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
config.getKeyTransparencyServiceConfiguration().port(),
|
||||
config.getKeyTransparencyServiceConfiguration().tlsCertificate(),
|
||||
config.getKeyTransparencyServiceConfiguration().clientCertificate(),
|
||||
config.getKeyTransparencyServiceConfiguration().clientPrivateKey().value(),
|
||||
keyTransparencyCallbackExecutor);
|
||||
config.getKeyTransparencyServiceConfiguration().clientPrivateKey().value());
|
||||
SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(svr2CredentialsGenerator,
|
||||
secureValueRecovery2ServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration());
|
||||
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator,
|
||||
|
||||
@@ -31,8 +31,7 @@ import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import org.glassfish.jersey.server.ManagedAsync;
|
||||
import org.signal.keytransparency.client.AciMonitorRequest;
|
||||
import org.signal.keytransparency.client.E164MonitorRequest;
|
||||
import org.signal.keytransparency.client.E164SearchRequest;
|
||||
@@ -48,15 +47,12 @@ import org.whispersystems.textsecuregcm.entities.KeyTransparencySearchResponse;
|
||||
import org.whispersystems.textsecuregcm.keytransparency.KeyTransparencyServiceClient;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitedByIp;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
||||
|
||||
@Path("/v1/key-transparency")
|
||||
@Tag(name = "KeyTransparency")
|
||||
public class KeyTransparencyController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(KeyTransparencyController.class);
|
||||
@VisibleForTesting
|
||||
static final Duration KEY_TRANSPARENCY_RPC_TIMEOUT = Duration.ofSeconds(15);
|
||||
private final KeyTransparencyServiceClient keyTransparencyServiceClient;
|
||||
|
||||
public KeyTransparencyController(
|
||||
@@ -88,6 +84,7 @@ public class KeyTransparencyController {
|
||||
@Path("/search")
|
||||
@RateLimitedByIp(RateLimiters.For.KEY_TRANSPARENCY_SEARCH_PER_IP)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ManagedAsync
|
||||
public KeyTransparencySearchResponse search(
|
||||
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
|
||||
@NotNull @Valid final KeyTransparencySearchRequest request) {
|
||||
@@ -104,19 +101,17 @@ public class KeyTransparencyController {
|
||||
.build()
|
||||
));
|
||||
|
||||
return keyTransparencyServiceClient.search(
|
||||
return new KeyTransparencySearchResponse(
|
||||
keyTransparencyServiceClient.search(
|
||||
ByteString.copyFrom(request.aci().toCompactByteArray()),
|
||||
ByteString.copyFrom(request.aciIdentityKey().serialize()),
|
||||
request.usernameHash().map(ByteString::copyFrom),
|
||||
maybeE164SearchRequest,
|
||||
request.lastTreeHeadSize(),
|
||||
request.distinguishedTreeHeadSize(),
|
||||
KEY_TRANSPARENCY_RPC_TIMEOUT)
|
||||
.thenApply(KeyTransparencySearchResponse::new).join();
|
||||
} catch (final CancellationException exception) {
|
||||
LOGGER.error("Unexpected cancellation from key transparency service", exception);
|
||||
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE, exception);
|
||||
} catch (final CompletionException exception) {
|
||||
request.distinguishedTreeHeadSize())
|
||||
.toByteArray());
|
||||
} catch (final StatusRuntimeException exception) {
|
||||
LOGGER.error("Unexpected error calling key transparency service", exception);
|
||||
handleKeyTransparencyServiceError(exception);
|
||||
}
|
||||
// This is unreachable
|
||||
@@ -140,6 +135,7 @@ public class KeyTransparencyController {
|
||||
@Path("/monitor")
|
||||
@RateLimitedByIp(RateLimiters.For.KEY_TRANSPARENCY_MONITOR_PER_IP)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ManagedAsync
|
||||
public KeyTransparencyMonitorResponse monitor(
|
||||
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
|
||||
@NotNull @Valid final KeyTransparencyMonitorRequest request) {
|
||||
@@ -173,13 +169,10 @@ public class KeyTransparencyController {
|
||||
usernameHashMonitorRequest,
|
||||
e164MonitorRequest,
|
||||
request.lastNonDistinguishedTreeHeadSize(),
|
||||
request.lastDistinguishedTreeHeadSize(),
|
||||
KEY_TRANSPARENCY_RPC_TIMEOUT).join());
|
||||
|
||||
} catch (final CancellationException exception) {
|
||||
LOGGER.error("Unexpected cancellation from key transparency service", exception);
|
||||
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE, exception);
|
||||
} catch (final CompletionException exception) {
|
||||
request.lastDistinguishedTreeHeadSize())
|
||||
.toByteArray());
|
||||
} catch (final StatusRuntimeException exception) {
|
||||
LOGGER.error("Unexpected error calling key transparency service", exception);
|
||||
handleKeyTransparencyServiceError(exception);
|
||||
}
|
||||
// This is unreachable
|
||||
@@ -202,6 +195,7 @@ public class KeyTransparencyController {
|
||||
@Path("/distinguished")
|
||||
@RateLimitedByIp(RateLimiters.For.KEY_TRANSPARENCY_DISTINGUISHED_PER_IP)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ManagedAsync
|
||||
public KeyTransparencyDistinguishedKeyResponse getDistinguishedKey(
|
||||
@Auth final Optional<AuthenticatedDevice> authenticatedAccount,
|
||||
|
||||
@@ -212,34 +206,26 @@ public class KeyTransparencyController {
|
||||
requireNotAuthenticated(authenticatedAccount);
|
||||
|
||||
try {
|
||||
return keyTransparencyServiceClient.getDistinguishedKey(lastTreeHeadSize, KEY_TRANSPARENCY_RPC_TIMEOUT)
|
||||
.thenApply(KeyTransparencyDistinguishedKeyResponse::new)
|
||||
.join();
|
||||
} catch (final CancellationException exception) {
|
||||
LOGGER.error("Unexpected cancellation from key transparency service", exception);
|
||||
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE, exception);
|
||||
} catch (final CompletionException exception) {
|
||||
return new KeyTransparencyDistinguishedKeyResponse(
|
||||
keyTransparencyServiceClient.getDistinguishedKey(lastTreeHeadSize)
|
||||
.toByteArray());
|
||||
} catch (final StatusRuntimeException exception) {
|
||||
LOGGER.error("Unexpected error calling key transparency service", exception);
|
||||
handleKeyTransparencyServiceError(exception);
|
||||
}
|
||||
// This is unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleKeyTransparencyServiceError(final CompletionException exception) {
|
||||
final Throwable unwrapped = ExceptionUtils.unwrap(exception);
|
||||
|
||||
if (unwrapped instanceof StatusRuntimeException e) {
|
||||
final Status.Code code = e.getStatus().getCode();
|
||||
final String description = e.getStatus().getDescription();
|
||||
switch (code) {
|
||||
case NOT_FOUND -> throw new NotFoundException(description);
|
||||
case PERMISSION_DENIED -> throw new ForbiddenException(description);
|
||||
case INVALID_ARGUMENT -> throw new WebApplicationException(description, 422);
|
||||
default -> throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR, unwrapped);
|
||||
}
|
||||
private void handleKeyTransparencyServiceError(final StatusRuntimeException exception) {
|
||||
final Status.Code code = exception.getStatus().getCode();
|
||||
final String description = exception.getStatus().getDescription();
|
||||
switch (code) {
|
||||
case NOT_FOUND -> throw new NotFoundException(description);
|
||||
case PERMISSION_DENIED -> throw new ForbiddenException(description);
|
||||
case INVALID_ARGUMENT -> throw new WebApplicationException(description, 422);
|
||||
default -> throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR, exception);
|
||||
}
|
||||
LOGGER.error("Unexpected key transparency service failure", unwrapped);
|
||||
throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR, unwrapped);
|
||||
}
|
||||
|
||||
private void requireNotAuthenticated(final Optional<AuthenticatedDevice> authenticatedAccount) {
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.grpc.Status;
|
||||
import org.signal.keytransparency.client.AciMonitorRequest;
|
||||
import org.signal.keytransparency.client.ConsistencyParameters;
|
||||
import org.signal.keytransparency.client.DistinguishedRequest;
|
||||
import org.signal.keytransparency.client.DistinguishedResponse;
|
||||
import org.signal.keytransparency.client.E164MonitorRequest;
|
||||
import org.signal.keytransparency.client.E164SearchRequest;
|
||||
import org.signal.keytransparency.client.MonitorRequest;
|
||||
import org.signal.keytransparency.client.MonitorResponse;
|
||||
import org.signal.keytransparency.client.SearchRequest;
|
||||
import org.signal.keytransparency.client.SearchResponse;
|
||||
import org.signal.keytransparency.client.SimpleKeyTransparencyQueryServiceGrpc;
|
||||
import org.signal.keytransparency.client.UsernameHashMonitorRequest;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.keytransparency.KeyTransparencyServiceClient;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
|
||||
public class KeyTransparencyGrpcService extends
|
||||
SimpleKeyTransparencyQueryServiceGrpc.KeyTransparencyQueryServiceImplBase {
|
||||
@VisibleForTesting
|
||||
static final int COMMITMENT_INDEX_LENGTH = 32;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final KeyTransparencyServiceClient client;
|
||||
|
||||
public KeyTransparencyGrpcService(final RateLimiters rateLimiters,
|
||||
final KeyTransparencyServiceClient client) {
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse search(final SearchRequest request) throws RateLimitExceededException {
|
||||
rateLimiters.getKeyTransparencySearchLimiter().validate(RequestAttributesUtil.getRemoteAddress().getHostAddress());
|
||||
return client.search(validateSearchRequest(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonitorResponse monitor(final MonitorRequest request) throws RateLimitExceededException {
|
||||
rateLimiters.getKeyTransparencyMonitorLimiter().validate(RequestAttributesUtil.getRemoteAddress().getHostAddress());
|
||||
return client.monitor(validateMonitorRequest(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistinguishedResponse distinguished(final DistinguishedRequest request) throws RateLimitExceededException {
|
||||
rateLimiters.getKeyTransparencyDistinguishedLimiter().validate(RequestAttributesUtil.getRemoteAddress().getHostAddress());
|
||||
// A client's very first distinguished request will not have a "last" parameter
|
||||
if (request.hasLast() && request.getLast() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Last tree head size must be positive").asRuntimeException();
|
||||
}
|
||||
return client.distinguished(request);
|
||||
}
|
||||
|
||||
private SearchRequest validateSearchRequest(final SearchRequest request) {
|
||||
if (request.hasE164SearchRequest()) {
|
||||
final E164SearchRequest e164SearchRequest = request.getE164SearchRequest();
|
||||
if (e164SearchRequest.getUnidentifiedAccessKey().isEmpty() != e164SearchRequest.getE164().isEmpty()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Unidentified access key and E164 must be provided together or not at all").asRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.getConsistency().hasDistinguished()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Must provide distinguished tree head size").asRuntimeException();
|
||||
}
|
||||
|
||||
validateConsistencyParameters(request.getConsistency());
|
||||
return request;
|
||||
}
|
||||
|
||||
private MonitorRequest validateMonitorRequest(final MonitorRequest request) {
|
||||
final AciMonitorRequest aciMonitorRequest = request.getAci();
|
||||
|
||||
try {
|
||||
AciServiceIdentifier.fromBytes(aciMonitorRequest.getAci().toByteArray());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Invalid ACI").asRuntimeException();
|
||||
}
|
||||
if (aciMonitorRequest.getEntryPosition() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Aci entry position must be positive").asRuntimeException();
|
||||
}
|
||||
if (aciMonitorRequest.getCommitmentIndex().size() != COMMITMENT_INDEX_LENGTH) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Aci commitment index must be 32 bytes").asRuntimeException();
|
||||
}
|
||||
|
||||
if (request.hasUsernameHash()) {
|
||||
final UsernameHashMonitorRequest usernameHashMonitorRequest = request.getUsernameHash();
|
||||
if (usernameHashMonitorRequest.getUsernameHash().isEmpty()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Username hash cannot be empty").asRuntimeException();
|
||||
}
|
||||
if (usernameHashMonitorRequest.getUsernameHash().size() != AccountController.USERNAME_HASH_LENGTH) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Invalid username hash length").asRuntimeException();
|
||||
}
|
||||
if (usernameHashMonitorRequest.getEntryPosition() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Username hash entry position must be positive").asRuntimeException();
|
||||
}
|
||||
if (usernameHashMonitorRequest.getCommitmentIndex().size() != COMMITMENT_INDEX_LENGTH) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Username hash commitment index must be 32 bytes").asRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
if (request.hasE164()) {
|
||||
final E164MonitorRequest e164MonitorRequest = request.getE164();
|
||||
if (e164MonitorRequest.getE164().isEmpty()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("E164 cannot be empty").asRuntimeException();
|
||||
}
|
||||
if (e164MonitorRequest.getEntryPosition() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("E164 entry position must be positive").asRuntimeException();
|
||||
}
|
||||
if (e164MonitorRequest.getCommitmentIndex().size() != COMMITMENT_INDEX_LENGTH) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("E164 commitment index must be 32 bytes").asRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.getConsistency().hasDistinguished() || !request.getConsistency().hasLast()) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Must provide distinguished and last tree head sizes").asRuntimeException();
|
||||
}
|
||||
|
||||
validateConsistencyParameters(request.getConsistency());
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void validateConsistencyParameters(final ConsistencyParameters consistency) {
|
||||
if (consistency.getDistinguished() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Distinguished tree head size must be positive").asRuntimeException();
|
||||
}
|
||||
|
||||
if (consistency.hasLast() && consistency.getLast() <= 0) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("Last tree head size must be positive").asRuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.whispersystems.textsecuregcm.keytransparency;
|
||||
|
||||
import com.google.protobuf.AbstractMessageLite;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import io.grpc.ChannelCredentials;
|
||||
@@ -20,44 +19,43 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.signal.keytransparency.client.AciMonitorRequest;
|
||||
import org.signal.keytransparency.client.ConsistencyParameters;
|
||||
import org.signal.keytransparency.client.DistinguishedRequest;
|
||||
import org.signal.keytransparency.client.DistinguishedResponse;
|
||||
import org.signal.keytransparency.client.E164MonitorRequest;
|
||||
import org.signal.keytransparency.client.E164SearchRequest;
|
||||
import org.signal.keytransparency.client.KeyTransparencyQueryServiceGrpc;
|
||||
import org.signal.keytransparency.client.MonitorRequest;
|
||||
import org.signal.keytransparency.client.MonitorResponse;
|
||||
import org.signal.keytransparency.client.SearchRequest;
|
||||
import org.signal.keytransparency.client.SearchResponse;
|
||||
import org.signal.keytransparency.client.UsernameHashMonitorRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
||||
import org.whispersystems.textsecuregcm.util.CompletableFutureUtil;
|
||||
|
||||
public class KeyTransparencyServiceClient implements Managed {
|
||||
|
||||
private static final String DAYS_UNTIL_CLIENT_CERTIFICATE_EXPIRATION_GAUGE_NAME =
|
||||
MetricsUtil.name(KeyTransparencyServiceClient.class, "daysUntilClientCertificateExpiration");
|
||||
private static final Duration KEY_TRANSPARENCY_RPC_TIMEOUT = Duration.ofSeconds(15);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KeyTransparencyServiceClient.class);
|
||||
|
||||
private final Executor callbackExecutor;
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final ChannelCredentials tlsChannelCredentials;
|
||||
private ManagedChannel channel;
|
||||
private KeyTransparencyQueryServiceGrpc.KeyTransparencyQueryServiceFutureStub stub;
|
||||
private KeyTransparencyQueryServiceGrpc.KeyTransparencyQueryServiceBlockingStub stub;
|
||||
|
||||
public KeyTransparencyServiceClient(
|
||||
final String host,
|
||||
final int port,
|
||||
final String tlsCertificate,
|
||||
final String clientCertificate,
|
||||
final String clientPrivateKey,
|
||||
final Executor callbackExecutor
|
||||
final String clientPrivateKey
|
||||
) throws IOException {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
@@ -76,7 +74,6 @@ public class KeyTransparencyServiceClient implements Managed {
|
||||
configureClientCertificateMetrics(clientCertificate);
|
||||
|
||||
}
|
||||
this.callbackExecutor = callbackExecutor;
|
||||
}
|
||||
|
||||
private void configureClientCertificateMetrics(String clientCertificate) {
|
||||
@@ -113,14 +110,13 @@ public class KeyTransparencyServiceClient implements Managed {
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public CompletableFuture<byte[]> search(
|
||||
public SearchResponse search(
|
||||
final ByteString aci,
|
||||
final ByteString aciIdentityKey,
|
||||
final Optional<ByteString> usernameHash,
|
||||
final Optional<E164SearchRequest> e164SearchRequest,
|
||||
final Optional<Long> lastTreeHeadSize,
|
||||
final long distinguishedTreeHeadSize,
|
||||
final Duration timeout) {
|
||||
final long distinguishedTreeHeadSize) {
|
||||
final SearchRequest.Builder searchRequestBuilder = SearchRequest.newBuilder()
|
||||
.setAci(aci)
|
||||
.setAciIdentityKey(aciIdentityKey);
|
||||
@@ -133,19 +129,20 @@ public class KeyTransparencyServiceClient implements Managed {
|
||||
lastTreeHeadSize.ifPresent(consistency::setLast);
|
||||
|
||||
searchRequestBuilder.setConsistency(consistency.build());
|
||||
return search(searchRequestBuilder.build());
|
||||
}
|
||||
|
||||
return CompletableFutureUtil.toCompletableFuture(stub.withDeadline(toDeadline(timeout))
|
||||
.search(searchRequestBuilder.build()), callbackExecutor)
|
||||
.thenApply(AbstractMessageLite::toByteArray);
|
||||
public SearchResponse search(final SearchRequest request) {
|
||||
return stub.withDeadline(toDeadline(KEY_TRANSPARENCY_RPC_TIMEOUT))
|
||||
.search(request);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public CompletableFuture<byte[]> monitor(final AciMonitorRequest aciMonitorRequest,
|
||||
public MonitorResponse monitor(final AciMonitorRequest aciMonitorRequest,
|
||||
final Optional<UsernameHashMonitorRequest> usernameHashMonitorRequest,
|
||||
final Optional<E164MonitorRequest> e164MonitorRequest,
|
||||
final long lastTreeHeadSize,
|
||||
final long distinguishedTreeHeadSize,
|
||||
final Duration timeout) {
|
||||
final long distinguishedTreeHeadSize) {
|
||||
final MonitorRequest.Builder monitorRequestBuilder = MonitorRequest.newBuilder()
|
||||
.setAci(aciMonitorRequest)
|
||||
.setConsistency(ConsistencyParameters.newBuilder()
|
||||
@@ -155,20 +152,26 @@ public class KeyTransparencyServiceClient implements Managed {
|
||||
|
||||
usernameHashMonitorRequest.ifPresent(monitorRequestBuilder::setUsernameHash);
|
||||
e164MonitorRequest.ifPresent(monitorRequestBuilder::setE164);
|
||||
|
||||
return CompletableFutureUtil.toCompletableFuture(stub.withDeadline(toDeadline(timeout))
|
||||
.monitor(monitorRequestBuilder.build()), callbackExecutor)
|
||||
.thenApply(AbstractMessageLite::toByteArray);
|
||||
return monitor(monitorRequestBuilder.build());
|
||||
}
|
||||
|
||||
public MonitorResponse monitor(final MonitorRequest request) {
|
||||
return stub.withDeadline(toDeadline(KEY_TRANSPARENCY_RPC_TIMEOUT))
|
||||
.monitor(request);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public CompletableFuture<byte[]> getDistinguishedKey(final Optional<Long> lastTreeHeadSize, final Duration timeout) {
|
||||
public DistinguishedResponse getDistinguishedKey(final Optional<Long> lastTreeHeadSize) {
|
||||
final DistinguishedRequest request = lastTreeHeadSize.map(
|
||||
last -> DistinguishedRequest.newBuilder().setLast(last).build())
|
||||
.orElseGet(DistinguishedRequest::getDefaultInstance);
|
||||
return CompletableFutureUtil.toCompletableFuture(stub.withDeadline(toDeadline(timeout)).distinguished(request),
|
||||
callbackExecutor)
|
||||
.thenApply(AbstractMessageLite::toByteArray);
|
||||
return distinguished(request);
|
||||
}
|
||||
|
||||
public DistinguishedResponse distinguished(final DistinguishedRequest request) {
|
||||
return stub.withDeadline(toDeadline(KEY_TRANSPARENCY_RPC_TIMEOUT))
|
||||
.distinguished(request);
|
||||
}
|
||||
|
||||
private static Deadline toDeadline(final Duration timeout) {
|
||||
@@ -180,7 +183,7 @@ public class KeyTransparencyServiceClient implements Managed {
|
||||
channel = Grpc.newChannelBuilderForAddress(host, port, tlsChannelCredentials)
|
||||
.idleTimeout(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
stub = KeyTransparencyQueryServiceGrpc.newFutureStub(channel);
|
||||
stub = KeyTransparencyQueryServiceGrpc.newBlockingStub(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -206,4 +206,16 @@ public class RateLimiters extends BaseRateLimiters<RateLimiters.For> {
|
||||
public RateLimiter getWaitForTransferArchiveLimiter() {
|
||||
return forDescriptor(For.WAIT_FOR_TRANSFER_ARCHIVE);
|
||||
}
|
||||
|
||||
public RateLimiter getKeyTransparencySearchLimiter() {
|
||||
return forDescriptor(For.KEY_TRANSPARENCY_SEARCH_PER_IP);
|
||||
}
|
||||
|
||||
public RateLimiter getKeyTransparencyDistinguishedLimiter() {
|
||||
return forDescriptor(For.KEY_TRANSPARENCY_DISTINGUISHED_PER_IP);
|
||||
}
|
||||
|
||||
public RateLimiter getKeyTransparencyMonitorLimiter() {
|
||||
return forDescriptor(For.KEY_TRANSPARENCY_MONITOR_PER_IP);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user