Allow empty files in transit tier info

This commit is contained in:
Max Radermacher 2026-05-11 12:51:27 -05:00 committed by GitHub
parent 3a348a5ea1
commit 0fb3e9b50e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 36 additions and 71 deletions

View File

@ -179,8 +179,8 @@ public class CVComponentGenericAttachment: CVComponentBase, CVComponent {
if let attachmentPointer = genericAttachment.attachmentPointer {
var textComponents = [String]()
if let byteCount = attachmentPointer.unencryptedByteCount, byteCount > 0 {
textComponents.append(OWSFormat.localizedFileSizeString(from: UInt64(safeCast: byteCount)))
if attachmentPointer.unencryptedByteCount > 0 {
textComponents.append(OWSFormat.localizedFileSizeString(from: UInt64(safeCast: attachmentPointer.unencryptedByteCount)))
}
switch genericAttachment.attachment {

View File

@ -513,7 +513,7 @@ extension ReferencedAttachment {
locatorInfo.integrityCheck = .plaintextHash(streamInfo.plaintextHash)
} else if let transitTierInfoToExport {
locatorInfo.key = attachment.encryptionKey
locatorInfo.size = transitTierInfoToExport.unencryptedByteCount ?? 0
locatorInfo.size = transitTierInfoToExport.unencryptedByteCount
// At the time of writing, TransitTierInfo.integrityCheck prefers
// the encrypted digest even if both are present. (See comment in

View File

@ -37,7 +37,7 @@ public class IncomingContactSyncJobQueue {
cdnKey: String,
attachmentKey: AttachmentKey,
digest: Data,
plaintextLength: UInt32?,
plaintextLength: UInt32,
isComplete: Bool,
tx: DBWriteTransaction,
) {

View File

@ -62,7 +62,7 @@ public final class IncomingContactSyncJobRecord: JobRecord {
cdnKey: String,
attachmentKey: AttachmentKey,
digest: Data,
plaintextLength: UInt32?,
plaintextLength: UInt32,
isCompleteContactSync: Bool,
failureCount: UInt = 0,
status: Status = .ready,

View File

@ -204,7 +204,7 @@ public class Attachment {
/// Expected byte count after decrypting the resource off the transit tier (and removing padding).
/// Provided by the sender of incoming attachments.
public let unencryptedByteCount: UInt32?
public let unencryptedByteCount: UInt32
/// Generated locally for outgoing attachments.
/// For incoming attachments, taken off the service proto. If validation fails, the download is rejected.
@ -458,9 +458,7 @@ public class Attachment {
cdnNumber: latestTransitTierInfo.cdnNumber,
key: latestTransitTierInfo.encryptionKey,
digest: digest,
// Okay to fall back to our local data length even if the original sender
// didn't include it; we now know it from the local file.
plaintextDataLength: latestTransitTierInfo.unencryptedByteCount ?? metadata.plaintextDataLength,
plaintextDataLength: latestTransitTierInfo.unencryptedByteCount,
// Encrypted length is the same regardless of the key used.
encryptedDataLength: metadata.encryptedDataLength,
),
@ -536,7 +534,8 @@ private extension Attachment.TransitTierInfo {
self.uploadTimestamp = uploadTimestamp
self.lastDownloadAttemptTimestamp = lastDownloadAttemptTimestamp
self.encryptionKey = encryptionKey
self.unencryptedByteCount = unencryptedByteCount
// Old clients may have incorrectly persisted `nil` when `0` was provided.
self.unencryptedByteCount = unencryptedByteCount ?? 0
self.integrityCheck = integrityCheck
if let incrementalMac, let incrementalMacChunkSize {
self.incrementalMacInfo = .init(mac: incrementalMac, chunkSize: incrementalMacChunkSize)

View File

@ -279,7 +279,7 @@ public class AttachmentManagerImpl: AttachmentManager {
throw OWSAssertionError("Missing digest")
}
return .init(
return Attachment.TransitTierInfo(
cdnNumber: cdnNumber,
cdnKey: cdnKey,
uploadTimestamp: proto.uploadTimestamp,
@ -341,7 +341,6 @@ public class AttachmentManagerImpl: AttachmentManager {
let contentType = Attachment.ContentType(mimeType: mimeType)
var attachmentRecord: Attachment.Record
let sourceUnencryptedByteCount: UInt32?
if
proto.hasLocatorInfo,
@ -353,12 +352,6 @@ public class AttachmentManagerImpl: AttachmentManager {
incrementalMacInfo: incrementalMacInfo,
)
if proto.locatorInfo.size > 0 {
sourceUnencryptedByteCount = proto.locatorInfo.size
} else {
sourceUnencryptedByteCount = nil
}
switch proto.locatorInfo.integrityCheck {
case .plaintextHash(let plaintextHash):
if plaintextHash.isEmpty {
@ -415,7 +408,6 @@ public class AttachmentManagerImpl: AttachmentManager {
mimeType: mimeType,
contentType: contentType,
)
sourceUnencryptedByteCount = nil
}
let referenceParams: AttachmentReference.ConstructionParams
@ -429,7 +421,7 @@ public class AttachmentManagerImpl: AttachmentManager {
caption: proto.hasCaption ? proto.caption : nil,
),
sourceFilename: sourceFilename,
sourceUnencryptedByteCount: sourceUnencryptedByteCount,
sourceUnencryptedByteCount: proto.locatorInfo.size,
sourceMediaSizePixels: sourceMediaSizePixels,
)
}
@ -441,24 +433,19 @@ public class AttachmentManagerImpl: AttachmentManager {
tx: tx,
)
if let sourceUnencryptedByteCount {
let estimatedMediaTierSize: UInt64
if
let size = Cryptography
.estimatedMediaTierCDNSize(unencryptedSize: UInt64(safeCast: sourceUnencryptedByteCount))
{
estimatedMediaTierSize = size
} else {
Logger.warn("Failed to get estimated media tier size for attachment \(attachment.id)!")
estimatedMediaTierSize = UInt64(UInt32.max)
}
attachmentByteCounter.addToByteCount(
attachmentID: attachment.id,
byteCount: estimatedMediaTierSize,
)
let estimatedMediaTierSize: UInt64
if let size = Cryptography.estimatedMediaTierCDNSize(unencryptedSize: UInt64(safeCast: proto.locatorInfo.size)) {
estimatedMediaTierSize = size
} else {
Logger.warn("Failed to get estimated media tier size for attachment \(attachment.id)!")
estimatedMediaTierSize = UInt64(UInt32.max)
}
attachmentByteCounter.addToByteCount(
attachmentID: attachment.id,
byteCount: estimatedMediaTierSize,
)
if let mediaName = attachmentRecord.mediaName {
orphanedBackupAttachmentScheduler.didCreateOrUpdateAttachment(
withMediaName: mediaName,
@ -504,13 +491,6 @@ public class AttachmentManagerImpl: AttachmentManager {
return nil
}
let unencryptedByteCount: UInt32?
if locatorInfo.size > 0 {
unencryptedByteCount = locatorInfo.size
} else {
unencryptedByteCount = nil
}
let uploadTimestampMs: UInt64
if locatorInfo.hasTransitTierUploadTimestamp, locatorInfo.transitTierUploadTimestamp > 0 {
uploadTimestampMs = locatorInfo.transitTierUploadTimestamp
@ -545,7 +525,7 @@ public class AttachmentManagerImpl: AttachmentManager {
cdnKey: locatorInfo.transitCdnKey,
uploadTimestamp: uploadTimestampMs,
encryptionKey: locatorInfo.key,
unencryptedByteCount: unencryptedByteCount,
unencryptedByteCount: locatorInfo.size,
integrityCheck: integrityCheck,
incrementalMacInfo: incrementalMacInfo,
lastDownloadAttemptTimestamp: nil,

View File

@ -51,13 +51,12 @@ public class AttachmentPointer {
].compacted().max()
}
public var unencryptedByteCount: UInt32? {
public var unencryptedByteCount: UInt32 {
switch source {
case .mediaTier(let mediaTier):
return mediaTier.unencryptedByteCount
case .transitTier(let transitTier):
return transitTier.unencryptedByteCount
?? AttachmentBackupPointer(attachment: attachment)?.unencryptedByteCount
}
}

View File

@ -270,24 +270,19 @@ extension ReferencedAttachment {
builder.setFlags(0)
}
let unencryptedByteCount: UInt32?
let pixelSize: CGSize?
if let streamInfo = pointer.attachment.streamInfo {
unencryptedByteCount = streamInfo.unencryptedByteCount
pixelSize = streamInfo.cachedMediaSizePixels
} else {
unencryptedByteCount = reference.sourceUnencryptedByteCount
pixelSize = reference.sourceMediaSizePixels
}
if let unencryptedByteCount {
builder.setSize(unencryptedByteCount)
}
if let pixelSize {
builder.setWidth(UInt32(pixelSize.width.rounded()))
builder.setHeight(UInt32(pixelSize.height.rounded()))
}
builder.setKey(pointer.info.encryptionKey)
builder.setSize(pointer.info.unencryptedByteCount)
builder.setDigest(ciphertextDigest)
builder.setUploadTimestamp(pointer.uploadTimestamp)

View File

@ -19,7 +19,7 @@ public class AttachmentTransitPointer {
public var cdnKey: String { info.cdnKey }
public var uploadTimestamp: UInt64 { info.uploadTimestamp }
public var lastDownloadAttemptTimestamp: UInt64? { info.lastDownloadAttemptTimestamp }
public var unencryptedByteCount: UInt32? { info.unencryptedByteCount }
public var unencryptedByteCount: UInt32 { info.unencryptedByteCount }
private init(
attachment: Attachment,

View File

@ -121,7 +121,7 @@ public protocol AttachmentContentValidator {
func validateDownloadedContents(
ofEncryptedFileAt fileUrl: URL,
attachmentKey: AttachmentKey,
plaintextLength: UInt32?,
plaintextLength: UInt32,
integrityCheck: AttachmentIntegrityCheck,
mimeType: String,
renderingFlag: AttachmentReference.RenderingFlag,

View File

@ -74,7 +74,7 @@ public class AttachmentContentValidatorImpl: AttachmentContentValidator {
public func validateDownloadedContents(
ofEncryptedFileAt fileUrl: URL,
attachmentKey inputAttachmentKey: AttachmentKey,
plaintextLength: UInt32?,
plaintextLength: UInt32,
integrityCheck: AttachmentIntegrityCheck,
mimeType: String,
renderingFlag: AttachmentReference.RenderingFlag,
@ -82,20 +82,15 @@ public class AttachmentContentValidatorImpl: AttachmentContentValidator {
) async throws -> PendingAttachment {
// Very very first thing: validate the integrity check.
// Throw if this fails.
var decryptedLength = 0
try Cryptography.decryptFile(
at: fileUrl,
metadata: DecryptionMetadata(
key: inputAttachmentKey,
integrityCheck: integrityCheck,
plaintextLength: plaintextLength.map(UInt64.init(safeCast:)),
plaintextLength: UInt64(safeCast: plaintextLength),
),
output: { data in
decryptedLength += data.count
},
output: { _ in },
)
let plaintextLength = plaintextLength ?? UInt32(decryptedLength)
let inputType = InputType.encryptedFile(
fileUrl,
inputAttachmentKey: inputAttachmentKey,

View File

@ -32,7 +32,7 @@ open class AttachmentContentValidatorMock: AttachmentContentValidator {
open func validateDownloadedContents(
ofEncryptedFileAt fileUrl: URL,
attachmentKey: AttachmentKey,
plaintextLength: UInt32?,
plaintextLength: UInt32,
integrityCheck: AttachmentIntegrityCheck,
mimeType: String,
renderingFlag: AttachmentReference.RenderingFlag,

View File

@ -814,12 +814,9 @@ public class AttachmentDownloadManagerImpl: AttachmentDownloadManager {
plaintextLength: transitTierInfo.unencryptedByteCount,
integrityCheck: transitTierInfo.integrityCheck,
)
downloadSizeSource = transitTierInfo.unencryptedByteCount.map({
.estimatedSizeBytes(Cryptography.estimatedTransitTierCDNSize(
unencryptedSize: UInt64(safeCast: $0),
) ?? { owsFail("can always produce estimate for 32-bit byte count") }())
}) ?? .useHeadRequest
downloadSizeSource = .estimatedSizeBytes(Cryptography.estimatedTransitTierCDNSize(
unencryptedSize: UInt64(safeCast: transitTierInfo.unencryptedByteCount),
) ?? { owsFail("can always produce estimate for 32-bit byte count") }())
let attachmentLimits = IncomingAttachmentLimits.currentLimits(remoteConfig: remoteConfigProvider.currentConfig())
switch attachment.contentType {
case .image:
@ -1997,7 +1994,7 @@ public class AttachmentDownloadManagerImpl: AttachmentDownloadManager {
case transitTier(
mimeType: String,
attachmentKey: AttachmentKey,
plaintextLength: UInt32?,
plaintextLength: UInt32,
integrityCheck: AttachmentIntegrityCheck,
)