diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index 3f2dbe1d2c..3931d11e34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -1690,10 +1690,10 @@ object BackupRepository { * * It's important to note that in order to get this to the archive cdn, you still need to use [copyAttachmentToArchive]. */ - fun getAttachmentUploadForm(): NetworkResult { + fun getAttachmentUploadForm(uploadLength: Long): NetworkResult { return initBackupAndFetchAuth() .then { credential -> - SignalNetwork.archive.getMediaUploadForm(SignalStore.account.requireAci(), credential.mediaBackupAccess) + SignalNetwork.archive.getMediaUploadForm(SignalStore.account.requireAci(), credential.mediaBackupAccess, uploadLength) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt index 8cba74c4ed..e7a1ebf835 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveThumbnailUploadJob.kt @@ -31,8 +31,10 @@ import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult +import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream +import org.whispersystems.signalservice.internal.crypto.PaddingInputStream import org.whispersystems.signalservice.internal.push.AttachmentUploadForm import java.io.ByteArrayInputStream import java.io.IOException @@ -176,7 +178,9 @@ class ArchiveThumbnailUploadJob private constructor( return Result.failure() } - val form: AttachmentUploadForm = when (val formResult = BackupRepository.getAttachmentUploadForm()) { + val ciphertextLength = AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(thumbnailResult.data.size.toLong())) + + val form: AttachmentUploadForm = when (val formResult = BackupRepository.getAttachmentUploadForm(ciphertextLength)) { is NetworkResult.Success -> formResult.result is NetworkResult.ApplicationError -> { Log.w(TAG, "Failed to get upload form due to an application error. Retrying.", formResult.throwable) @@ -192,6 +196,13 @@ class ArchiveThumbnailUploadJob private constructor( Log.w(TAG, "Rate limited when getting upload form.") Result.retry(formResult.retryAfter()?.inWholeMilliseconds ?: defaultBackoff()) } + 413 -> { + Log.w(TAG, "Thumbnail is too large to upload to the archive. Marking as a permanent failure.") + ArchiveDatabaseExecutor.runBlocking { + SignalDatabase.attachments.setArchiveThumbnailTransferState(attachmentId, AttachmentTable.ArchiveTransferState.PERMANENT_FAILURE) + } + Result.failure() + } else -> { Log.w(TAG, "Failed to get upload form with status code ${formResult.code}") Result.retry(defaultBackoff()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt index ffc71ef8b5..70fff62fbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt @@ -36,9 +36,11 @@ import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.archive.ArchiveMediaUploadFormStatusCodes import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult +import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil import org.whispersystems.signalservice.api.messages.AttachmentTransferProgress import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException +import org.whispersystems.signalservice.internal.crypto.PaddingInputStream import org.whispersystems.signalservice.internal.push.AttachmentUploadForm import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec import java.io.FileNotFoundException @@ -230,8 +232,10 @@ class UploadAttachmentToArchiveJob private constructor( val existingSpec = uploadSpec?.let { ResumableUploadSpec.from(it) } + val ciphertextLength = AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(attachment.size)) + val form: AttachmentUploadForm? = if (existingSpec == null) { - when (val formResult = BackupRepository.getAttachmentUploadForm()) { + when (val formResult = BackupRepository.getAttachmentUploadForm(ciphertextLength)) { is NetworkResult.Success -> formResult.result is NetworkResult.ApplicationError -> { Log.w(TAG, "[$attachmentId]$mediaIdLog Failed to get upload form due to an application error.", formResult.throwable) @@ -248,6 +252,13 @@ class UploadAttachmentToArchiveJob private constructor( Log.w(TAG, "[$attachmentId]$mediaIdLog Rate limited when getting upload form.") Result.retry(formResult.retryAfter()?.inWholeMilliseconds ?: defaultBackoff()) } + ArchiveMediaUploadFormStatusCodes.MediaTooLarge -> { + Log.w(TAG, "[$attachmentId]$mediaIdLog Media is too large to upload to the archive. Marking as a permanent failure.") + ArchiveDatabaseExecutor.runBlocking { + setArchiveTransferStateWithDelayedNotification(attachmentId, AttachmentTable.ArchiveTransferState.PERMANENT_FAILURE) + } + Result.failure() + } else -> Result.retry(defaultBackoff()) } } diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveMediaUploadFormStatusCodes.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveMediaUploadFormStatusCodes.kt index 50d4f54c88..060eaebb90 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveMediaUploadFormStatusCodes.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/archive/ArchiveMediaUploadFormStatusCodes.kt @@ -14,6 +14,7 @@ enum class ArchiveMediaUploadFormStatusCodes(val code: Int) { BadArguments(400), InvalidPresentationOrSignature(401), InsufficientPermissions(403), + MediaTooLarge(413), RateLimited(429), Unknown(-1); diff --git a/lib/network/src/main/java/org/signal/network/api/ArchiveApi.kt b/lib/network/src/main/java/org/signal/network/api/ArchiveApi.kt index 4e9faf4c88..a1aff89e29 100644 --- a/lib/network/src/main/java/org/signal/network/api/ArchiveApi.kt +++ b/lib/network/src/main/java/org/signal/network/api/ArchiveApi.kt @@ -303,13 +303,14 @@ class ArchiveApi( * - 200: Success * - 400: Bad request, or made on authenticated channel * - 403: Forbidden + * - 413: The media is too large * - 429: Rate-limited */ - fun getMediaUploadForm(aci: ACI, archiveServiceAccess: ArchiveServiceAccess): NetworkResult { + fun getMediaUploadForm(aci: ACI, archiveServiceAccess: ArchiveServiceAccess, uploadLength: Long): NetworkResult { return getCredentialPresentation(aci, archiveServiceAccess) .map { it.toArchiveCredentialPresentation().toHeaders() } .then { headers -> - val request = WebSocketRequestMessage.get("/v1/archives/media/upload/form", headers) + val request = WebSocketRequestMessage.get("/v1/archives/media/upload/form?uploadLength=$uploadLength", headers) NetworkResult.fromWebSocketRequest(unauthWebSocket, request, AttachmentUploadForm::class) } }