Do most of the proto and database groundwork for the new mediaName.

This commit is contained in:
Greyson Parrelli
2025-06-20 11:47:54 -04:00
committed by Cody Henthorne
parent e705495638
commit 38c8f852bf
431 changed files with 600 additions and 781 deletions

View File

@@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.ArchiveCommitAttachmentDeletesJob
import org.thoughtcrime.securesms.jobs.ArchiveThumbnailUploadJob
import org.thoughtcrime.securesms.jobs.BackfillDigestJob
import org.thoughtcrime.securesms.jobs.UploadAttachmentToArchiveJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
@@ -109,7 +108,6 @@ object ArchiveUploadProgress {
)
}
AppDependencies.jobManager.cancelAllInQueue(BackfillDigestJob.QUEUE)
AppDependencies.jobManager.cancelAllInQueue(ArchiveCommitAttachmentDeletesJob.ARCHIVE_ATTACHMENT_QUEUE)
UploadAttachmentToArchiveJob.getAllQueueKeys().forEach {
AppDependencies.jobManager.cancelAllInQueue(it)
@@ -126,7 +124,7 @@ object ArchiveUploadProgress {
Log.d(TAG, "Flushing job manager queue...")
AppDependencies.jobManager.flush()
val queues = setOf(BackfillDigestJob.QUEUE, ArchiveThumbnailUploadJob.KEY, ArchiveCommitAttachmentDeletesJob.ARCHIVE_ATTACHMENT_QUEUE) + UploadAttachmentToArchiveJob.getAllQueueKeys()
val queues = setOf(ArchiveThumbnailUploadJob.KEY, ArchiveCommitAttachmentDeletesJob.ARCHIVE_ATTACHMENT_QUEUE) + UploadAttachmentToArchiveJob.getAllQueueKeys()
Log.d(TAG, "Waiting for cancelations to occur...")
while (!AppDependencies.jobManager.areQueuesEmpty(queues)) {
delay(1.seconds)

View File

@@ -691,7 +691,7 @@ object BackupRepository {
val localArchivableAttachments = dbSnapshot
.attachmentTable
.getLocalArchivableAttachments()
.associateBy { MediaName.fromDigest(it.remoteDigest) }
.associateBy { MediaName.fromPlaintextHashAndRemoteKey(it.plaintextHash, it.remoteKey) }
localBackupProgressEmitter.onAttachment(0, localArchivableAttachments.size.toLong())
@@ -1965,13 +1965,14 @@ class ArchiveMediaItemIterator(private val cursor: Cursor) : Iterator<ArchiveMed
override fun hasNext(): Boolean = !cursor.isAfterLast
override fun next(): ArchiveMediaItem {
val digest = cursor.requireNonNullBlob(AttachmentTable.REMOTE_DIGEST)
val plaintextHash = cursor.requireNonNullBlob(AttachmentTable.DATA_HASH_END)
val remoteKey = cursor.requireNonNullBlob(AttachmentTable.REMOTE_KEY)
val cdn = cursor.requireIntOrNull(AttachmentTable.ARCHIVE_CDN)
val mediaId = MediaName.fromDigest(digest).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
val thumbnailMediaId = MediaName.fromDigestForThumbnail(digest).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
val mediaId = MediaName.fromPlaintextHashAndRemoteKey(plaintextHash, remoteKey).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
val thumbnailMediaId = MediaName.fromPlaintextHashAndRemoteKeyForThumbnail(plaintextHash, remoteKey).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
cursor.moveToNext()
return ArchiveMediaItem(mediaId, thumbnailMediaId, cdn, digest)
return ArchiveMediaItem(mediaId, thumbnailMediaId, cdn, plaintextHash, remoteKey)
}
}

View File

@@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.backup.v2
import android.text.TextUtils
import org.signal.core.util.Base64
import org.signal.core.util.Base64.decodeBase64
import org.signal.core.util.Base64.decodeBase64OrThrow
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
import org.thoughtcrime.securesms.database.AttachmentTable
@@ -22,8 +24,8 @@ import java.util.Optional
object DatabaseAttachmentArchiveUtil {
@JvmStatic
fun requireMediaName(attachment: DatabaseAttachment): MediaName {
require(isDigestValidated(attachment))
return MediaName.fromDigest(attachment.remoteDigest!!)
require(hadIntegrityCheckPerformed(attachment))
return MediaName.fromPlaintextHashAndRemoteKey(attachment.dataHash!!.decodeBase64OrThrow(), attachment.remoteKey!!.decodeBase64OrThrow())
}
/**
@@ -31,14 +33,21 @@ object DatabaseAttachmentArchiveUtil {
*/
@JvmStatic
fun requireMediaNameAsString(attachment: DatabaseAttachment): String {
require(isDigestValidated(attachment))
return MediaName.fromDigest(attachment.remoteDigest!!).name
require(hadIntegrityCheckPerformed(attachment))
return MediaName.fromPlaintextHashAndRemoteKey(attachment.dataHash!!.decodeBase64OrThrow(), attachment.remoteKey!!.decodeBase64OrThrow()).name
}
@JvmStatic
fun getMediaName(attachment: DatabaseAttachment): MediaName? {
return if (isDigestValidated(attachment)) {
attachment.remoteDigest?.let { MediaName.fromDigest(it) }
return if (hadIntegrityCheckPerformed(attachment)) {
val plaintextHash = attachment.dataHash.decodeBase64()
val remoteKey = attachment.remoteKey?.decodeBase64()
if (plaintextHash != null && remoteKey != null) {
MediaName.fromPlaintextHashAndRemoteKey(plaintextHash, remoteKey)
} else {
null
}
} else {
null
}
@@ -46,11 +55,11 @@ object DatabaseAttachmentArchiveUtil {
@JvmStatic
fun requireThumbnailMediaName(attachment: DatabaseAttachment): MediaName {
require(isDigestValidated(attachment))
return MediaName.fromDigestForThumbnail(attachment.remoteDigest!!)
require(hadIntegrityCheckPerformed(attachment))
return MediaName.fromPlaintextHashAndRemoteKeyForThumbnail(attachment.dataHash!!.decodeBase64OrThrow(), attachment.remoteKey!!.decodeBase64OrThrow())
}
private fun isDigestValidated(attachment: DatabaseAttachment): Boolean {
private fun hadIntegrityCheckPerformed(attachment: DatabaseAttachment): Boolean {
return when (attachment.transferState) {
AttachmentTable.TRANSFER_PROGRESS_DONE,
AttachmentTable.TRANSFER_NEEDS_RESTORE,

View File

@@ -63,7 +63,7 @@ object LocalArchiver {
return@localExport
}
val mediaName = MediaName.fromDigest(attachment.remoteDigest)
val mediaName = MediaName.fromPlaintextHashAndRemoteKey(attachment.plaintextHash, attachment.remoteKey)
mediaNames.add(mediaName)
@@ -73,7 +73,6 @@ object LocalArchiver {
}
source()?.use { sourceStream ->
val iv = attachment.remoteIv
val combinedKey = Base64.decode(attachment.remoteKey)
val destination: OutputStream? = filesFileSystem.fileOutputStream(mediaName)
@@ -84,7 +83,7 @@ object LocalArchiver {
// todo [local-backup] but deal with attachment disappearing/deleted by normal app use
try {
PaddingInputStream(sourceStream, attachment.size).use { input ->
AttachmentCipherOutputStream(combinedKey, iv, destination).use { output ->
AttachmentCipherOutputStream(combinedKey, null, destination).use { output ->
StreamUtil.copy(input, output)
}
}

View File

@@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.whispersystems.signalservice.api.backup.MediaName
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.api.util.UuidUtil
@@ -48,81 +47,84 @@ fun FilePointer?.toLocalAttachment(
): Attachment? {
if (this == null || this.locatorInfo == null) return null
val hasMediaName = this.locatorInfo.mediaName.isNotEmpty()
val hasTransitInfo = this.locatorInfo.transitCdnKey != null
val attachmentType = when {
this.locatorInfo.plaintextHash != null -> AttachmentType.ARCHIVE
this.locatorInfo.encryptedDigest != null && this.locatorInfo.transitCdnKey != null -> AttachmentType.TRANSIT
else -> AttachmentType.INVALID
}
if (hasTransitInfo && !hasMediaName) {
val signalAttachmentPointer = SignalServiceAttachmentPointer(
cdnNumber = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
remoteId = SignalServiceAttachmentRemoteId.from(locatorInfo.transitCdnKey),
contentType = contentType,
key = this.locatorInfo.key.toByteArray(),
size = Optional.ofNullable(locatorInfo.size),
preview = Optional.empty(),
width = this.width ?: 0,
height = this.height ?: 0,
digest = Optional.ofNullable(this.locatorInfo.digest.toByteArray()),
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
fileName = Optional.ofNullable(fileName),
voiceNote = voiceNote,
isBorderless = borderless,
isGif = gif,
caption = Optional.ofNullable(this.caption),
blurHash = Optional.ofNullable(this.blurHash),
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp?.clampToValidBackupRange() ?: 0,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
return PointerAttachment.forPointer(
pointer = Optional.of(signalAttachmentPointer),
stickerLocator = stickerLocator,
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
).orNull()
} else if (!hasMediaName) {
return TombstoneAttachment(
contentType = contentType,
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
fileName = this.fileName,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = quote,
stickerLocator = stickerLocator,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
} else {
return ArchivedAttachment(
contentType = contentType,
size = this.locatorInfo.size.toLong(),
cdn = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp ?: 0,
key = this.locatorInfo.key.toByteArray(),
iv = null,
cdnKey = this.locatorInfo.transitCdnKey?.nullIfBlank(),
archiveCdn = this.locatorInfo.mediaTierCdnNumber,
archiveMediaName = this.locatorInfo.mediaName,
archiveMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName(this.locatorInfo.mediaName)).encode(),
archiveThumbnailMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.locatorInfo.mediaName)).encode(),
digest = this.locatorInfo.digest.toByteArray(),
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = quote,
stickerLocator = stickerLocator,
uuid = UuidUtil.fromByteStringOrNull(uuid),
fileName = fileName
)
return when (attachmentType) {
AttachmentType.ARCHIVE -> {
ArchivedAttachment(
contentType = contentType,
size = this.locatorInfo.size.toLong(),
cdn = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp ?: 0,
key = this.locatorInfo.key.toByteArray(),
cdnKey = this.locatorInfo.transitCdnKey?.nullIfBlank(),
archiveCdn = this.locatorInfo.mediaTierCdnNumber,
plaintextHash = this.locatorInfo.plaintextHash!!.toByteArray(),
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
stickerLocator = stickerLocator,
gif = gif,
quote = quote,
uuid = UuidUtil.fromByteStringOrNull(uuid),
fileName = fileName
)
}
AttachmentType.TRANSIT -> {
val signalAttachmentPointer = SignalServiceAttachmentPointer(
cdnNumber = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
remoteId = SignalServiceAttachmentRemoteId.from(locatorInfo.transitCdnKey!!),
contentType = contentType,
key = this.locatorInfo.key.toByteArray(),
size = Optional.ofNullable(locatorInfo.size),
preview = Optional.empty(),
width = this.width ?: 0,
height = this.height ?: 0,
digest = Optional.ofNullable(this.locatorInfo.encryptedDigest!!.toByteArray()),
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
fileName = Optional.ofNullable(fileName),
voiceNote = voiceNote,
isBorderless = borderless,
isGif = gif,
caption = Optional.ofNullable(this.caption),
blurHash = Optional.ofNullable(this.blurHash),
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp?.clampToValidBackupRange() ?: 0,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
PointerAttachment.forPointer(
pointer = Optional.of(signalAttachmentPointer),
stickerLocator = stickerLocator,
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
).orNull()
}
AttachmentType.INVALID -> {
TombstoneAttachment(
contentType = contentType,
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
fileName = this.fileName,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = quote,
stickerLocator = stickerLocator,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
}
}
}
@@ -192,21 +194,17 @@ fun FilePointer.Builder.setLegacyLocators(attachment: DatabaseAttachment, mediaA
}
fun DatabaseAttachment.toLocatorInfo(): FilePointer.LocatorInfo {
if (this.remoteKey.isNullOrBlank() || this.remoteDigest == null || this.size == 0L) {
return FilePointer.LocatorInfo()
}
val attachmentType = this.toRemoteAttachmentType()
if (this.transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE && this.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED) {
if (attachmentType == AttachmentType.INVALID) {
return FilePointer.LocatorInfo()
}
val locatorBuilder = FilePointer.LocatorInfo.Builder()
val remoteKey = Base64.decode(this.remoteKey).toByteString()
val archiveMediaName = this.getMediaName()?.toString()
val remoteKey = Base64.decode(this.remoteKey!!).toByteString()
locatorBuilder.key = remoteKey
locatorBuilder.digest = this.remoteDigest.toByteString()
locatorBuilder.size = this.size.toInt()
if (this.remoteLocation.isNotNullOrBlank()) {
@@ -215,8 +213,17 @@ fun DatabaseAttachment.toLocatorInfo(): FilePointer.LocatorInfo {
locatorBuilder.transitTierUploadTimestamp = this.uploadTimestamp.takeIf { it > 0 }?.clampToValidBackupRange()
}
locatorBuilder.mediaTierCdnNumber = this.archiveCdn?.takeIf { archiveMediaName != null }
locatorBuilder.mediaName = archiveMediaName.emptyIfNull()
@Suppress("KotlinConstantConditions")
when (attachmentType) {
AttachmentType.ARCHIVE -> {
locatorBuilder.plaintextHash = Base64.decode(this.dataHash!!).toByteString()
locatorBuilder.mediaTierCdnNumber = this.archiveCdn
}
AttachmentType.TRANSIT -> {
locatorBuilder.encryptedDigest = this.remoteDigest!!.toByteString()
}
AttachmentType.INVALID -> Unit
}
return locatorBuilder.build()
}
@@ -260,3 +267,30 @@ fun RemoteAvatarColor.toLocal(): AvatarColor {
RemoteAvatarColor.A210 -> AvatarColor.A210
}
}
private fun DatabaseAttachment.toRemoteAttachmentType(): AttachmentType {
if (this.remoteKey.isNullOrBlank()) {
return AttachmentType.INVALID
}
if (this.transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE && this.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED) {
return AttachmentType.INVALID
}
val activelyOnArchiveCdn = this.archiveTransferState == AttachmentTable.ArchiveTransferState.FINISHED
val couldBeOnArchiveCdn = this.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE && this.archiveTransferState != AttachmentTable.ArchiveTransferState.PERMANENT_FAILURE
if (this.dataHash != null && (activelyOnArchiveCdn || couldBeOnArchiveCdn)) {
return AttachmentType.ARCHIVE
}
if (this.remoteDigest != null && this.remoteLocation.isNotNullOrBlank()) {
return AttachmentType.TRANSIT
}
return AttachmentType.INVALID
}
private enum class AttachmentType {
TRANSIT, ARCHIVE, INVALID
}