Add IV to the attachment table.

This commit is contained in:
Greyson Parrelli
2024-08-30 12:11:22 -04:00
committed by Cody Henthorne
parent 07289b417b
commit 4b47d38d78
26 changed files with 534 additions and 309 deletions

View File

@@ -15,7 +15,6 @@ import org.signal.core.util.Base64
import org.signal.core.util.update
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.backup.v2.BackupRepository.getMediaName
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -27,7 +26,9 @@ import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.backup.MediaId
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.File
import java.util.UUID
@@ -661,7 +662,7 @@ class AttachmentTableTest_deduping {
}
fun upload(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()) {
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createPointerAttachment(attachmentId, uploadTimestamp), uploadTimestamp)
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createUploadResult(attachmentId, uploadTimestamp))
val attachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
SignalDatabase.attachments.setArchiveData(
@@ -763,6 +764,7 @@ class AttachmentTableTest_deduping {
assertEquals(lhsAttachment.remoteLocation, rhsAttachment.remoteLocation)
assertEquals(lhsAttachment.remoteKey, rhsAttachment.remoteKey)
assertArrayEquals(lhsAttachment.remoteIv, rhsAttachment.remoteIv)
assertArrayEquals(lhsAttachment.remoteDigest, rhsAttachment.remoteDigest)
assertArrayEquals(lhsAttachment.incrementalDigest, rhsAttachment.incrementalDigest)
assertEquals(lhsAttachment.incrementalMacChunkSize, rhsAttachment.incrementalMacChunkSize)
@@ -796,36 +798,19 @@ class AttachmentTableTest_deduping {
return MediaStream(this.inputStream(), MediaUtil.IMAGE_JPEG, 2, 2)
}
private fun createPointerAttachment(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()): PointerAttachment {
val location = "somewhere-${Random.nextLong()}"
val key = "somekey-${Random.nextLong()}"
val digest = Random.nextBytes(32)
val incrementalDigest = Random.nextBytes(16)
private fun createUploadResult(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()): AttachmentUploadResult {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
return PointerAttachment(
"image/jpeg",
AttachmentTable.TRANSFER_PROGRESS_DONE,
databaseAttachment.size, // size
null,
Cdn.CDN_3, // cdnNumber
location,
key,
digest,
incrementalDigest,
5, // incrementalMacChunkSize
null,
databaseAttachment.voiceNote,
databaseAttachment.borderless,
databaseAttachment.videoGif,
databaseAttachment.width,
databaseAttachment.height,
uploadTimestamp,
databaseAttachment.caption,
databaseAttachment.stickerLocator,
databaseAttachment.blurHash,
databaseAttachment.uuid
return AttachmentUploadResult(
remoteId = SignalServiceAttachmentRemoteId.V4("somewhere-${Random.nextLong()}"),
cdnNumber = Cdn.CDN_3.cdnNumber,
key = databaseAttachment.remoteKey?.let { Base64.decode(it) } ?: Util.getSecretBytes(64),
iv = databaseAttachment.remoteIv ?: Util.getSecretBytes(16),
digest = Random.nextBytes(32),
incrementalDigest = Random.nextBytes(16),
incrementalDigestChunkSize = 5,
uploadTimestamp = uploadTimestamp,
dataSize = databaseAttachment.size
)
}
}

View File

@@ -13,6 +13,7 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
@@ -33,6 +34,9 @@ import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.util.IdentityUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import java.util.UUID
@Suppress("ClassName")
@@ -574,30 +578,35 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
// Has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[0].attachmentId,
attachment = attachments[0].copy(digest = byteArrayOf(attachments[0].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
uploadResult = attachments[0].toUploadResult(
digest = byteArrayOf(attachments[0].attachmentId.id.toByte()),
uploadTimestamp = message1.timestamp + 1
)
)
// Missing uuid and digest
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[1].attachmentId,
attachment = attachments[1],
uploadTimestamp = message1.timestamp + 1
uploadResult = attachments[1].toUploadResult(uploadTimestamp = message1.timestamp + 1)
)
// Missing uuid and plain text
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[2].attachmentId,
attachment = attachments[2].copy(digest = byteArrayOf(attachments[2].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
uploadResult = attachments[2].toUploadResult(
digest = byteArrayOf(attachments[2].attachmentId.id.toByte()),
uploadTimestamp = message1.timestamp + 1
)
)
SignalDatabase.rawDatabase.update(AttachmentTable.TABLE_NAME).values(AttachmentTable.DATA_HASH_END to null).where("${AttachmentTable.ID} = ?", attachments[2].attachmentId).run()
// Different has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[3].attachmentId,
attachment = attachments[3].copy(digest = byteArrayOf(attachments[3].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
uploadResult = attachments[3].toUploadResult(
digest = byteArrayOf(attachments[3].attachmentId.id.toByte()),
uploadTimestamp = message1.timestamp + 1
)
)
attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
@@ -674,6 +683,7 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
cdn = this.cdn,
location = this.remoteLocation,
key = this.remoteKey,
iv = this.remoteIv,
digest = digest,
incrementalDigest = this.incrementalDigest,
incrementalMacChunkSize = this.incrementalMacChunkSize,
@@ -700,4 +710,21 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe {
uuid = uuid
)
}
private fun Attachment.toUploadResult(
digest: ByteArray = this.remoteDigest ?: byteArrayOf(),
uploadTimestamp: Long = this.uploadTimestamp
): AttachmentUploadResult {
return AttachmentUploadResult(
remoteId = SignalServiceAttachmentRemoteId.V4(this.remoteLocation ?: "some-location"),
cdnNumber = this.cdn.cdnNumber,
key = this.remoteKey?.let { Base64.decode(it) } ?: Util.getSecretBytes(64),
iv = this.remoteIv ?: Util.getSecretBytes(16),
digest = digest,
incrementalDigest = this.incrementalDigest,
incrementalDigestChunkSize = this.incrementalMacChunkSize,
dataSize = this.size,
uploadTimestamp = uploadTimestamp
)
}
}

View File

@@ -32,6 +32,7 @@ class ArchivedAttachment : Attachment {
size: Long,
cdn: Int,
key: ByteArray,
iv: ByteArray?,
cdnKey: String?,
archiveCdn: Int?,
archiveMediaName: String,
@@ -60,6 +61,7 @@ class ArchivedAttachment : Attachment {
cdn = Cdn.fromCdnNumber(cdn),
remoteLocation = cdnKey,
remoteKey = Base64.encodeWithoutPadding(key),
remoteIv = iv,
remoteDigest = digest,
incrementalDigest = incrementalMac,
fastPreflightId = null,

View File

@@ -37,6 +37,8 @@ abstract class Attachment(
@JvmField
val remoteKey: String?,
@JvmField
val remoteIv: ByteArray?,
@JvmField
val remoteDigest: ByteArray?,
@JvmField
val incrementalDigest: ByteArray?,
@@ -86,6 +88,7 @@ abstract class Attachment(
cdn = Cdn.deserialize(parcel.readInt()),
remoteLocation = parcel.readString(),
remoteKey = parcel.readString(),
remoteIv = ParcelUtil.readByteArray(parcel),
remoteDigest = ParcelUtil.readByteArray(parcel),
incrementalDigest = ParcelUtil.readByteArray(parcel),
fastPreflightId = parcel.readString(),

View File

@@ -58,6 +58,7 @@ class DatabaseAttachment : Attachment {
cdn: Cdn,
location: String?,
key: String?,
iv: ByteArray?,
digest: ByteArray?,
incrementalDigest: ByteArray?,
incrementalMacChunkSize: Int,
@@ -90,6 +91,7 @@ class DatabaseAttachment : Attachment {
cdn = cdn,
remoteLocation = location,
remoteKey = key,
remoteIv = iv,
remoteDigest = digest,
incrementalDigest = incrementalDigest,
fastPreflightId = fastPreflightId,

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.attachments
import android.net.Uri
import android.os.Parcel
import androidx.annotation.VisibleForTesting
import org.signal.core.util.Base64.encodeWithPadding
import org.signal.core.util.Base64
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.stickers.StickerLocator
@@ -24,6 +24,7 @@ class PointerAttachment : Attachment {
cdn: Cdn,
location: String,
key: String?,
iv: ByteArray?,
digest: ByteArray?,
incrementalDigest: ByteArray?,
incrementalMacChunkSize: Int,
@@ -46,6 +47,7 @@ class PointerAttachment : Attachment {
cdn = cdn,
remoteLocation = location,
remoteKey = key,
remoteIv = iv,
remoteDigest = digest,
incrementalDigest = incrementalDigest,
fastPreflightId = fastPreflightId,
@@ -86,12 +88,17 @@ class PointerAttachment : Attachment {
@JvmStatic
@JvmOverloads
fun forPointer(pointer: Optional<SignalServiceAttachment>, stickerLocator: StickerLocator? = null, fastPreflightId: String? = null, transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING): Optional<Attachment> {
fun forPointer(
pointer: Optional<SignalServiceAttachment>,
stickerLocator: StickerLocator? = null,
fastPreflightId: String? = null,
transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING
): Optional<Attachment> {
if (!pointer.isPresent || !pointer.get().isPointer()) {
return Optional.empty()
}
val encodedKey: String? = pointer.get().asPointer().key?.let { encodeWithPadding(it) }
val encodedKey: String? = pointer.get().asPointer().key?.let { Base64.encodeWithPadding(it) }
return Optional.of(
PointerAttachment(
@@ -102,6 +109,7 @@ class PointerAttachment : Attachment {
cdn = Cdn.fromCdnNumber(pointer.get().asPointer().cdnNumber),
location = pointer.get().asPointer().remoteId.toString(),
key = encodedKey,
iv = null,
digest = pointer.get().asPointer().digest.orElse(null),
incrementalDigest = pointer.get().asPointer().incrementalDigest.orElse(null),
incrementalMacChunkSize = pointer.get().asPointer().incrementalMacChunkSize,
@@ -139,7 +147,8 @@ class PointerAttachment : Attachment {
fileName = quotedAttachment.fileName,
cdn = Cdn.fromCdnNumber(thumbnail?.asPointer()?.cdnNumber ?: 0),
location = thumbnail?.asPointer()?.remoteId?.toString() ?: "0",
key = thumbnail?.asPointer()?.key?.let { encodeWithPadding(it) },
key = thumbnail?.asPointer()?.key?.let { Base64.encodeWithPadding(it) },
iv = null,
digest = thumbnail?.asPointer()?.digest?.orElse(null),
incrementalDigest = thumbnail?.asPointer()?.incrementalDigest?.orElse(null),
incrementalMacChunkSize = thumbnail?.asPointer()?.incrementalMacChunkSize ?: 0,

View File

@@ -22,6 +22,7 @@ class TombstoneAttachment : Attachment {
cdn = Cdn.CDN_0,
remoteLocation = null,
remoteKey = null,
remoteIv = null,
remoteDigest = null,
incrementalDigest = null,
fastPreflightId = null,
@@ -62,6 +63,7 @@ class TombstoneAttachment : Attachment {
cdn = Cdn.CDN_0,
remoteLocation = null,
remoteKey = null,
remoteIv = null,
remoteDigest = null,
incrementalDigest = incrementalMac,
fastPreflightId = null,

View File

@@ -75,6 +75,7 @@ class UriAttachment : Attachment {
cdn = Cdn.CDN_0,
remoteLocation = null,
remoteKey = null,
remoteIv = null,
remoteDigest = null,
incrementalDigest = null,
fastPreflightId = fastPreflightId,

View File

@@ -1030,6 +1030,7 @@ class ChatItemImportInserter(
size = this.backupLocator.size.toLong(),
cdn = this.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
key = this.backupLocator.key.toByteArray(),
iv = null,
cdnKey = this.backupLocator.transitCdnKey,
archiveCdn = this.backupLocator.cdnNumber,
archiveMediaName = this.backupLocator.mediaName,

View File

@@ -90,7 +90,9 @@ import org.thoughtcrime.securesms.util.FileUtils
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.StorageUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.util.JsonUtil
import java.io.File
@@ -118,6 +120,7 @@ class AttachmentTable(
const val MESSAGE_ID = "message_id"
const val CONTENT_TYPE = "content_type"
const val REMOTE_KEY = "remote_key"
const val REMOTE_IV = "remote_iv"
const val REMOTE_LOCATION = "remote_location"
const val REMOTE_DIGEST = "remote_digest"
const val REMOTE_INCREMENTAL_DIGEST = "remote_incremental_digest"
@@ -178,6 +181,7 @@ class AttachmentTable(
MESSAGE_ID,
CONTENT_TYPE,
REMOTE_KEY,
REMOTE_IV,
REMOTE_LOCATION,
REMOTE_DIGEST,
REMOTE_INCREMENTAL_DIGEST,
@@ -263,7 +267,8 @@ class AttachmentTable(
$THUMBNAIL_FILE TEXT DEFAULT NULL,
$THUMBNAIL_RANDOM BLOB DEFAULT NULL,
$THUMBNAIL_RESTORE_STATE INTEGER DEFAULT ${ThumbnailRestoreState.NONE.value},
$ATTACHMENT_UUID TEXT DEFAULT NULL
$ATTACHMENT_UUID TEXT DEFAULT NULL,
$REMOTE_IV BLOB DEFAULT NULL
)
"""
@@ -1026,7 +1031,7 @@ class AttachmentTable(
* it's ending hash, which is critical for backups.
*/
@Throws(IOException::class)
fun finalizeAttachmentAfterUpload(id: AttachmentId, attachment: Attachment, uploadTimestamp: Long) {
fun finalizeAttachmentAfterUpload(id: AttachmentId, uploadResult: AttachmentUploadResult) {
Log.i(TAG, "[finalizeAttachmentAfterUpload] Finalizing upload for $id.")
val dataStream = getAttachmentStream(id, 0)
@@ -1040,17 +1045,14 @@ class AttachmentTable(
val values = contentValuesOf(
TRANSFER_STATE to TRANSFER_PROGRESS_DONE,
CDN_NUMBER to attachment.cdn.serialize(),
REMOTE_LOCATION to attachment.remoteLocation,
REMOTE_DIGEST to attachment.remoteDigest,
REMOTE_INCREMENTAL_DIGEST to attachment.incrementalDigest,
REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE to attachment.incrementalMacChunkSize,
REMOTE_KEY to attachment.remoteKey,
DATA_SIZE to attachment.size,
CDN_NUMBER to uploadResult.cdnNumber,
REMOTE_LOCATION to uploadResult.remoteId.toString(),
REMOTE_DIGEST to uploadResult.digest,
REMOTE_INCREMENTAL_DIGEST to uploadResult.incrementalDigest,
REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE to uploadResult.incrementalDigestChunkSize,
DATA_SIZE to uploadResult.dataSize,
DATA_HASH_END to dataHashEnd,
FAST_PREFLIGHT_ID to attachment.fastPreflightId,
BLUR_HASH to attachment.getVisualHashStringOrNull(),
UPLOAD_TIMESTAMP to uploadTimestamp
UPLOAD_TIMESTAMP to uploadResult.uploadTimestamp
)
val dataFilePath = getDataFilePath(id) ?: throw IOException("No data file found for attachment!")
@@ -1153,6 +1155,25 @@ class AttachmentTable(
}
}
fun createKeyIvIfNecessary(attachmentId: AttachmentId) {
val key = Util.getSecretBytes(64)
val iv = Util.getSecretBytes(16)
writableDatabase.withinTransaction {
writableDatabase
.update(TABLE_NAME)
.values(REMOTE_KEY to Base64.encodeWithPadding(key))
.where("$ID = ? AND $REMOTE_KEY IS NULL", attachmentId.id)
.run()
writableDatabase
.update(TABLE_NAME)
.values(REMOTE_IV to iv)
.where("$ID = ? AND $REMOTE_IV IS NULL", attachmentId.id)
.run()
}
}
/**
* Inserts new attachments in the table. The [Attachment]s may or may not have data, depending on whether it's an attachment we created locally or some
* inbound attachment that we haven't fetched yet.
@@ -1507,6 +1528,7 @@ class AttachmentTable(
cdn = Cdn.deserialize(jsonObject.getInt(CDN_NUMBER)),
location = jsonObject.getString(REMOTE_LOCATION),
key = jsonObject.getString(REMOTE_KEY),
iv = null,
digest = null,
incrementalDigest = null,
incrementalMacChunkSize = 0,
@@ -2040,6 +2062,7 @@ class AttachmentTable(
contentValues.put(REMOTE_INCREMENTAL_DIGEST, uploadTemplate?.incrementalDigest)
contentValues.put(REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE, uploadTemplate?.incrementalMacChunkSize ?: 0)
contentValues.put(REMOTE_KEY, uploadTemplate?.remoteKey)
contentValues.put(REMOTE_IV, uploadTemplate?.remoteIv)
contentValues.put(FILE_NAME, StorageUtil.getCleanFileName(attachment.fileName))
contentValues.put(FAST_PREFLIGHT_ID, attachment.fastPreflightId)
contentValues.put(VOICE_NOTE, if (attachment.voiceNote) 1 else 0)
@@ -2120,6 +2143,7 @@ class AttachmentTable(
cdn = cursor.requireObject(CDN_NUMBER, Cdn.Serializer),
location = cursor.requireString(REMOTE_LOCATION),
key = cursor.requireString(REMOTE_KEY),
iv = cursor.requireBlob(REMOTE_IV),
digest = cursor.requireBlob(REMOTE_DIGEST),
incrementalDigest = cursor.requireBlob(REMOTE_INCREMENTAL_DIGEST),
incrementalMacChunkSize = cursor.requireInt(REMOTE_INCREMENTAL_DIGEST_CHUNK_SIZE),

View File

@@ -101,6 +101,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V240_MessageFullTex
import org.thoughtcrime.securesms.database.helpers.migration.V241_ExpireTimerVersion
import org.thoughtcrime.securesms.database.helpers.migration.V242_MessageFullTextSearchEmojiSupportV2
import org.thoughtcrime.securesms.database.helpers.migration.V243_MessageFullTextSearchDisableSecureDelete
import org.thoughtcrime.securesms.database.helpers.migration.V244_AttachmentRemoteIv
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -204,10 +205,11 @@ object SignalDatabaseMigrations {
240 to V240_MessageFullTextSearchSecureDelete,
241 to V241_ExpireTimerVersion,
242 to V242_MessageFullTextSearchEmojiSupportV2,
243 to V243_MessageFullTextSearchDisableSecureDelete
243 to V243_MessageFullTextSearchDisableSecureDelete,
244 to V244_AttachmentRemoteIv
)
const val DATABASE_VERSION = 243
const val DATABASE_VERSION = 244
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Adds the remoteIv column to attachments.
*/
object V244_AttachmentRemoteIv : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE attachment ADD COLUMN remote_iv BLOB DEFAULT NULL;")
}
}

View File

@@ -8,11 +8,9 @@ package org.thoughtcrime.securesms.jobs
import org.greenrobot.eventbus.EventBus
import org.signal.core.util.logging.Log
import org.signal.protos.resumableuploads.ResumableUpload
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.AttachmentUploadUtil
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.BackupV2Event
import org.thoughtcrime.securesms.database.AttachmentTable
@@ -24,9 +22,8 @@ import org.thoughtcrime.securesms.jobs.protos.ArchiveAttachmentBackfillJobData
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.archive.ArchiveMediaResponse
import org.whispersystems.signalservice.api.archive.ArchiveMediaUploadFormStatusCodes
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import java.io.IOException
import java.util.Optional
import kotlin.time.Duration.Companion.days
/**
@@ -159,16 +156,16 @@ class ArchiveAttachmentBackfillJob private constructor(
}
Log.d(TAG, "Beginning upload...")
val remoteAttachment: SignalServiceAttachmentPointer = try {
AppDependencies.signalServiceMessageSender.uploadAttachment(attachmentStream)
} catch (e: IOException) {
Log.w(TAG, "Failed to upload $attachmentId", e)
return Result.retry(defaultBackoff())
val attachmentApi = AppDependencies.signalServiceMessageSender.attachmentApi
val uploadResult: AttachmentUploadResult = when (val result = attachmentApi.uploadAttachmentV4(attachmentStream)) {
is NetworkResult.Success -> result.result
is NetworkResult.ApplicationError -> throw result.throwable
is NetworkResult.NetworkError -> return Result.retry(defaultBackoff())
is NetworkResult.StatusCodeError -> return Result.retry(defaultBackoff())
}
Log.d(TAG, "Upload complete!")
val pointerAttachment: Attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, attachmentRecord.fastPreflightId).get()
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentRecord.attachmentId, pointerAttachment, remoteAttachment.uploadTimestamp)
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentRecord.attachmentId, uploadResult)
SignalDatabase.attachments.setArchiveTransferState(attachmentRecord.attachmentId, AttachmentTable.ArchiveTransferState.BACKFILL_UPLOADED)
attachmentRecord = SignalDatabase.attachments.getAttachment(attachmentRecord.attachmentId)

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.jobs
import android.text.TextUtils
import okhttp3.internal.http2.StreamResetException
import org.greenrobot.eventbus.EventBus
import org.signal.core.util.Base64
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.inRoundedDays
import org.signal.core.util.logging.Log
@@ -17,7 +18,6 @@ import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.AttachmentUploadUtil
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.net.NotPushRegisteredException
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.AttachmentProgressService
import org.thoughtcrime.securesms.util.RemoteConfig
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
@@ -39,7 +40,6 @@ import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumab
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
import java.io.IOException
import java.util.Optional
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
@@ -136,7 +136,10 @@ class AttachmentUploadJob private constructor(
throw NotPushRegisteredException()
}
SignalDatabase.attachments.createKeyIvIfNecessary(attachmentId)
val messageSender = AppDependencies.signalServiceMessageSender
val attachmentApi = messageSender.attachmentApi
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId) ?: throw InvalidAttachmentException("Cannot find the specified attachment.")
val timeSinceUpload = System.currentTimeMillis() - databaseAttachment.uploadTimestamp
@@ -154,7 +157,17 @@ class AttachmentUploadJob private constructor(
if (uploadSpec == null) {
Log.d(TAG, "Need an upload spec. Fetching...")
uploadSpec = AppDependencies.signalServiceMessageSender.getResumableUploadSpec().toProto()
uploadSpec = attachmentApi
.getAttachmentV4UploadForm()
.then { form ->
attachmentApi.getResumableUploadSpec(
key = Base64.decode(databaseAttachment.remoteKey!!),
iv = databaseAttachment.remoteIv!!,
uploadForm = form
)
}
.successOrThrow()
.toProto()
} else {
Log.d(TAG, "Re-using existing upload spec.")
}
@@ -163,9 +176,8 @@ class AttachmentUploadJob private constructor(
try {
getAttachmentNotificationIfNeeded(databaseAttachment).use { notification ->
buildAttachmentStream(databaseAttachment, notification, uploadSpec!!).use { localAttachment ->
val remoteAttachment = messageSender.uploadAttachment(localAttachment)
val attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.fastPreflightId).get()
SignalDatabase.attachments.finalizeAttachmentAfterUpload(databaseAttachment.attachmentId, attachment, remoteAttachment.uploadTimestamp)
val uploadResult: AttachmentUploadResult = attachmentApi.uploadAttachmentV4(localAttachment).successOrThrow()
SignalDatabase.attachments.finalizeAttachmentAfterUpload(databaseAttachment.attachmentId, uploadResult)
ArchiveThumbnailUploadJob.enqueueIfNecessary(databaseAttachment.attachmentId)
}
}

View File

@@ -240,6 +240,7 @@ class UploadDependencyGraphTest {
cdn = attachment.cdn,
location = attachment.remoteLocation,
key = attachment.remoteKey,
iv = attachment.remoteIv,
digest = attachment.remoteDigest,
incrementalDigest = attachment.incrementalDigest,
incrementalMacChunkSize = attachment.incrementalMacChunkSize,

View File

@@ -40,6 +40,7 @@ object FakeMessageRecords {
cdnNumber: Int = 3,
location: String = "",
key: String = "",
iv: ByteArray = byteArrayOf(),
relay: String = "",
digest: ByteArray = byteArrayOf(),
incrementalDigest: ByteArray = byteArrayOf(),
@@ -67,42 +68,43 @@ object FakeMessageRecords {
thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState = AttachmentTable.ThumbnailRestoreState.NONE
): DatabaseAttachment {
return DatabaseAttachment(
attachmentId,
mmsId,
hasData,
hasThumbnail,
hasArchiveThumbnail,
contentType,
transferProgress,
size,
fileName,
Cdn.fromCdnNumber(cdnNumber),
location,
key,
digest,
incrementalDigest,
incrementalMacChunkSize,
fastPreflightId,
voiceNote,
borderless,
videoGif,
width,
height,
quote,
caption,
stickerLocator,
blurHash,
audioHash,
transformProperties,
displayOrder,
uploadTimestamp,
dataHash,
archiveCdn,
archiveThumbnailCdn,
archiveMediaId,
archiveMediaName,
thumbnailRestoreState,
null
attachmentId = attachmentId,
mmsId = mmsId,
hasData = hasData,
hasThumbnail = hasThumbnail,
hasArchiveThumbnail = hasArchiveThumbnail,
contentType = contentType,
transferProgress = transferProgress,
size = size,
fileName = fileName,
cdn = Cdn.fromCdnNumber(cdnNumber),
location = location,
key = key,
iv = iv,
digest = digest,
incrementalDigest = incrementalDigest,
incrementalMacChunkSize = incrementalMacChunkSize,
fastPreflightId = fastPreflightId,
voiceNote = voiceNote,
borderless = borderless,
videoGif = videoGif,
width = width,
height = height,
quote = quote,
caption = caption,
stickerLocator = stickerLocator,
blurHash = blurHash,
audioHash = audioHash,
transformProperties = transformProperties,
displayOrder = displayOrder,
uploadTimestamp = uploadTimestamp,
dataHash = dataHash,
archiveCdn = archiveCdn,
archiveThumbnailCdn = archiveThumbnailCdn,
archiveMediaName = archiveMediaId,
archiveMediaId = archiveMediaName,
thumbnailRestoreState = thumbnailRestoreState,
uuid = null
)
}