Consolidate CGDataProviderDirectCallbacks logic

This commit is contained in:
Max Radermacher 2026-04-29 14:07:51 -05:00 committed by GitHub
parent 018cb71ab4
commit 8668d86a35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 92 additions and 121 deletions

View File

@ -771,6 +771,7 @@
508622AD2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508622AC2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift */; };
508C72242C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508C72232C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift */; };
508F0346296F72F4001D88D0 /* CustomCellBackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508F0345296F72F4001D88D0 /* CustomCellBackgroundColor.swift */; };
508F05A52FA28308004B96E5 /* CGDataProvider+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508F05A42FA28308004B96E5 /* CGDataProvider+SSK.swift */; };
509085B82C498C3F00409B85 /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C5CABC289453B200548EEE /* HTMLMetadata.swift */; };
509085BA2C498C4400409B85 /* HTMLMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F94261D4289B1B5400460798 /* HTMLMetadataTests.swift */; };
509085BC2C498D3600409B85 /* LinkPreviewFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509085BB2C498D3500409B85 /* LinkPreviewFetcher.swift */; };
@ -5050,6 +5051,7 @@
508622AC2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanonicalPhoneNumberTest.swift; sourceTree = "<group>"; };
508C72232C2DFCB2000811F3 /* OWSOutgoingResendResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSOutgoingResendResponseTest.swift; sourceTree = "<group>"; };
508F0345296F72F4001D88D0 /* CustomCellBackgroundColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCellBackgroundColor.swift; sourceTree = "<group>"; };
508F05A42FA28308004B96E5 /* CGDataProvider+SSK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGDataProvider+SSK.swift"; sourceTree = "<group>"; };
509085BB2C498D3500409B85 /* LinkPreviewFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewFetcher.swift; sourceTree = "<group>"; };
509085BD2C49C29400409B85 /* PaddingBucket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingBucket.swift; sourceTree = "<group>"; };
509085BF2C49C2A500409B85 /* PaddingBucketTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingBucketTest.swift; sourceTree = "<group>"; };
@ -15456,6 +15458,7 @@
E7D7C93E28B580AC003F043B /* Bundle+OWS.swift */,
88D7BA9D266809F50088D1C2 /* CallMessageRelay.swift */,
76387BEF28F4ED73002C7BA5 /* CaseIterable.swift */,
508F05A42FA28308004B96E5 /* CGDataProvider+SSK.swift */,
F9C5CB2A289453B200548EEE /* Collection+OWS.swift */,
0512145A2C5BCECF0021EEC9 /* CollectionDifference+SSK.swift */,
34A955AB271B521500B05242 /* CommonStrings.swift */,
@ -19174,6 +19177,7 @@
668A012A2C2B6088007B8808 /* Catchable.swift in Sources */,
F9C5CC0C289453B300548EEE /* CDNDownloadOperation.swift in Sources */,
727328072CA6CF570080E2C7 /* Certificates.swift in Sources */,
508F05A52FA28308004B96E5 /* CGDataProvider+SSK.swift in Sources */,
661396AD28BE74DC00E0C4DF /* ChainedPromise.swift in Sources */,
50E5E4B32993352C00E15A1C /* ChangePhoneNumberPniManager.swift in Sources */,
6603AC2D29C220F30079BC82 /* ChangePhoneNumberPniManagerMock.swift in Sources */,

View File

@ -119,7 +119,7 @@ public struct DecryptionMetadata {
/// A read-only file handle to a file that is encrypted on disk but reads out plaintext bytes.
///
/// Functionally behaves like a FileHandle to a virtual plaintext file.
public protocol EncryptedFileHandle {
public protocol EncryptedFileHandle: CGDataProviderFileHandle {
/// Length, in bytes, of the decrypted plaintext.
/// Comes from the sender; otherwise we don't know where content ends and custom padding begins.
@ -136,6 +136,12 @@ public protocol EncryptedFileHandle {
func read(upToCount: Int) throws -> Data
}
extension EncryptedFileHandle {
public func readNonOptional(upToCount count: Int) throws -> Data {
return try self.read(upToCount: count)
}
}
enum CryptographyError: Error {
/// The caller-provided plaintext length was longer than the encrypted
/// length.

View File

@ -94,16 +94,6 @@ extension UIImage {
}
extension CGDataProvider {
// Class-bound wrapper around EncryptedFileHandle
class EncryptedFileHandleWrapper {
let fileHandle: SignalServiceKit.EncryptedFileHandle
init(_ fileHandle: SignalServiceKit.EncryptedFileHandle) {
self.fileHandle = fileHandle
}
}
/// If no plaintext length is provided, the file is assumed to only use pkcs7 padding.
fileprivate static func loadFromEncryptedFile<T>(
at fileURL: URL,
@ -124,57 +114,11 @@ extension CGDataProvider {
attachmentKey: attachmentKey,
)
}
let dataProvider = try CGDataProvider.from(fileHandle: fileHandle)
return try block(dataProvider)
}
public static func from(fileHandle: EncryptedFileHandle) throws -> CGDataProvider {
let fileHandle = EncryptedFileHandleWrapper(fileHandle)
var callbacks = CGDataProviderDirectCallbacks(
version: 0,
getBytePointer: nil,
releaseBytePointer: nil,
getBytesAtPosition: { info, buffer, offset, byteCount in
guard let info else {
return 0
}
let unmanagedFileHandle = Unmanaged<EncryptedFileHandleWrapper>.fromOpaque(info)
let fileHandle = unmanagedFileHandle.takeUnretainedValue().fileHandle
do {
if offset != fileHandle.offset() {
try fileHandle.seek(toOffset: UInt64(offset))
}
let data = try fileHandle.read(upToCount: byteCount)
data.withUnsafeBytes { bytes in
buffer.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
}
return data.count
} catch {
return 0
}
},
releaseInfo: { info in
guard let info else {
return
}
let unmanagedFileHandle = Unmanaged<EncryptedFileHandleWrapper>.fromOpaque(info)
unmanagedFileHandle.release()
},
)
let unmanagedFileHandle = Unmanaged.passRetained(fileHandle)
guard
let dataProvider = CGDataProvider(
directInfo: unmanagedFileHandle.toOpaque(),
size: Int64(fileHandle.fileHandle.plaintextLength),
callbacks: &callbacks,
)
else {
throw OWSAssertionError("Failed to create data provider")
let dataProvider = CGDataProvider.from(fileHandle: fileHandle, fileSize: Int64(fileHandle.plaintextLength))
guard let dataProvider else {
throw OWSAssertionError("couldn't initialize encrypted data provider")
}
return dataProvider
return try block(dataProvider)
}
}

View File

@ -0,0 +1,70 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public protocol CGDataProviderFileHandle {
func offset() throws -> UInt64
func seek(toOffset offset: UInt64) throws
func readNonOptional(upToCount count: Int) throws -> Data
}
extension FileHandle: CGDataProviderFileHandle {
public func readNonOptional(upToCount count: Int) throws -> Data {
return try self.read(upToCount: count) ?? Data()
}
}
extension CGDataProvider {
// Class-bound wrapper around a FileHandle
private class FileHandleWrapper {
let fileHandle: any CGDataProviderFileHandle
init(_ fileHandle: any CGDataProviderFileHandle) {
self.fileHandle = fileHandle
}
}
public static func from(fileHandle: any CGDataProviderFileHandle, fileSize: Int64) -> CGDataProvider? {
var callbacks = CGDataProviderDirectCallbacks(
version: 0,
getBytePointer: nil,
releaseBytePointer: nil,
getBytesAtPosition: { info, buffer, offset, byteCount in
guard let info else {
return 0
}
let fileHandle = Unmanaged<FileHandleWrapper>.fromOpaque(info).takeUnretainedValue().fileHandle
do {
if offset != (try fileHandle.offset()) {
try fileHandle.seek(toOffset: UInt64(offset))
}
let data = try fileHandle.readNonOptional(upToCount: byteCount)
data.withUnsafeBytes { bytes in
buffer.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
}
return data.count
} catch {
return 0
}
},
releaseInfo: { info in
guard let info else {
return
}
Unmanaged<FileHandleWrapper>.fromOpaque(info).release()
},
)
let fileHandleWrapper = FileHandleWrapper(fileHandle)
let unmanagedFileHandle = Unmanaged.passRetained(fileHandleWrapper)
return CGDataProvider(
directInfo: unmanagedFileHandle.toOpaque(),
size: fileSize,
callbacks: &callbacks,
)
}
}

View File

@ -46,7 +46,10 @@ struct EncryptedFileHandleImageSource: OWSImageSource {
}
func cgImageSource() throws -> CGImageSource? {
let dataProvider = try CGDataProvider.from(fileHandle: fileHandle)
let dataProvider = CGDataProvider.from(fileHandle: fileHandle, fileSize: Int64(fileHandle.plaintextLength))
guard let dataProvider else {
throw OWSAssertionError("couldn't initialize encrypted data provider")
}
return CGImageSourceCreateWithDataProvider(dataProvider, nil)
}
}

View File

@ -29,66 +29,10 @@ public struct FileHandleImageSource: OWSImageSource {
return try fileHandle.read(upToCount: byteLength) ?? Data()
}
// Class-bound wrapper around FileHandle
class FileHandleWrapper {
let fileHandle: FileHandle
init(_ fileHandle: FileHandle) {
self.fileHandle = fileHandle
}
}
public func cgImageSource() throws -> CGImageSource? {
let fileHandle = FileHandleWrapper(fileHandle)
var callbacks = CGDataProviderDirectCallbacks(
version: 0,
getBytePointer: nil,
releaseBytePointer: nil,
getBytesAtPosition: { info, buffer, offset, byteCount in
guard
let unmanagedFileHandle = info?.assumingMemoryBound(
to: Unmanaged<FileHandleWrapper>.self,
).pointee
else {
return 0
}
let fileHandle = unmanagedFileHandle.takeUnretainedValue().fileHandle
do {
if offset != (try fileHandle.offset()) {
try fileHandle.seek(toOffset: UInt64(offset))
}
let data = try fileHandle.read(upToCount: byteCount) ?? Data()
data.withUnsafeBytes { bytes in
buffer.copyMemory(from: bytes.baseAddress!, byteCount: bytes.count)
}
return data.count
} catch {
return 0
}
},
releaseInfo: { info in
guard
let unmanagedFileHandle = info?.assumingMemoryBound(
to: Unmanaged<FileHandleWrapper>.self,
).pointee
else {
return
}
unmanagedFileHandle.release()
},
)
var unmanagedFileHandle = Unmanaged.passRetained(fileHandle)
guard
let dataProvider = CGDataProvider(
directInfo: &unmanagedFileHandle,
size: Int64(byteLength),
callbacks: &callbacks,
)
else {
throw OWSAssertionError("Failed to create data provider")
let dataProvider = CGDataProvider.from(fileHandle: self.fileHandle, fileSize: Int64(self.byteLength))
guard let dataProvider else {
throw OWSAssertionError("couldn't create data provider")
}
return CGImageSourceCreateWithDataProvider(dataProvider, nil)
}