Allow optional size parameter when requesting message backup upload forms

This commit is contained in:
Ravi Khadiwala
2025-07-14 14:30:36 -05:00
committed by ravi-signal
parent ae2d98750c
commit 3f62677176
7 changed files with 139 additions and 4 deletions

View File

@@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.backup;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.util.DataSize;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
@@ -54,6 +55,7 @@ public class BackupManager {
static final String MESSAGE_BACKUP_NAME = "messageBackup";
public static final long MAX_TOTAL_BACKUP_MEDIA_BYTES = DataSize.gibibytes(100).toBytes();
public static final long MAX_MESSAGE_BACKUP_OBJECT_SIZE = DataSize.mebibytes(101).toBytes();
public static final long MAX_MEDIA_OBJECT_SIZE = DataSize.mebibytes(101).toBytes();
// If the last media usage recalculation is over MAX_QUOTA_STALENESS, force a recalculation before quota enforcement.

View File

@@ -516,12 +516,24 @@ public class ArchiveController {
@Parameter(description = BackupAuthCredentialPresentationSignature.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull
@HeaderParam(X_SIGNAL_ZK_AUTH_SIGNATURE) final BackupAuthCredentialPresentationSignature signature) {
@HeaderParam(X_SIGNAL_ZK_AUTH_SIGNATURE) final BackupAuthCredentialPresentationSignature signature,
@Parameter(description = "The size of the message backup to upload in bytes")
@QueryParam("uploadLength") final Optional<Long> uploadLength) {
if (account.isPresent()) {
throw new BadRequestException("must not use authenticated connection for anonymous operations");
}
return backupManager.authenticateBackupUser(presentation.presentation, signature.signature, userAgent)
.thenCompose(backupManager::createMessageBackupUploadDescriptor)
.thenCompose(backupUser -> {
final boolean oversize = uploadLength
.map(length -> length > BackupManager.MAX_MESSAGE_BACKUP_OBJECT_SIZE)
.orElse(false);
backupMetrics.updateMessageBackupSizeDistribution(backupUser, oversize, uploadLength);
if (oversize) {
throw new ClientErrorException("exceeded maximum uploadLength", Response.Status.REQUEST_ENTITY_TOO_LARGE);
}
return backupManager.createMessageBackupUploadDescriptor(backupUser);
})
.thenApply(result -> new UploadDescriptorResponse(
result.cdn(),
result.key(),

View File

@@ -101,7 +101,18 @@ public class BackupsAnonymousGrpcService extends ReactorBackupsAnonymousGrpc.Bac
public Mono<GetUploadFormResponse> getUploadForm(final GetUploadFormRequest request) {
return authenticateBackupUserMono(request.getSignedPresentation())
.flatMap(backupUser -> switch (request.getUploadTypeCase()) {
case MESSAGES -> Mono.fromFuture(backupManager.createMessageBackupUploadDescriptor(backupUser));
case MESSAGES -> {
final long uploadLength = request.getMessages().getUploadLength();
final boolean oversize = uploadLength > BackupManager.MAX_MESSAGE_BACKUP_OBJECT_SIZE;
backupMetrics.updateMessageBackupSizeDistribution(backupUser, oversize, Optional.of(uploadLength));
if (oversize) {
yield Mono.error(Status.FAILED_PRECONDITION
.withDescription("Exceeds max upload length")
.asRuntimeException());
}
yield Mono.fromFuture(backupManager.createMessageBackupUploadDescriptor(backupUser));
}
case MEDIA -> Mono.fromCompletionStage(backupManager.createTemporaryAttachmentUploadDescriptor(backupUser));
case UPLOADTYPE_NOT_SET -> Mono.error(Status.INVALID_ARGUMENT
.withDescription("Must set upload_type")

View File

@@ -8,17 +8,21 @@ package org.whispersystems.textsecuregcm.metrics;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import org.signal.libsignal.zkgroup.backups.BackupCredentialType;
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
import org.whispersystems.textsecuregcm.backup.CopyResult;
import java.util.Optional;
public class BackupMetrics {
private final static String COPY_MEDIA_COUNTER_NAME = name(BackupMetrics.class, "copyMedia");
private final static String GET_BACKUP_CREDENTIALS_NAME = name(BackupMetrics.class, "getBackupCredentials");
private final static String MESSAGE_BACKUP_SIZE_NAME = name(BackupMetrics.class, "messageBackupSize");
private MeterRegistry registry;
@@ -48,4 +52,19 @@ public class BackupMetrics {
.increment();
}
public void updateMessageBackupSizeDistribution(
AuthenticatedBackupUser authenticatedBackupUser,
final boolean oversize,
final Optional<Long> backupLength) {
DistributionSummary.builder(MESSAGE_BACKUP_SIZE_NAME)
.publishPercentileHistogram(true)
.tags(Tags.of(
UserAgentTagUtil.getPlatformTag(authenticatedBackupUser.userAgent()),
Tag.of("tier", authenticatedBackupUser.backupLevel().name().toLowerCase()),
Tag.of("oversize", Boolean.toString(oversize)),
Tag.of("hasBackupLength", Boolean.toString(backupLength.isPresent()))))
.register(Metrics.globalRegistry)
.record(backupLength.orElse(0L));
}
}