diff --git a/Package.swift b/Package.swift index a0681a0f..1ed8960e 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,6 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/21-DOT-DEV/swift-secp256k1", exact: "0.18.0"), - .package(url: "https://github.com/1024jp/GzipSwift", from: "6.1.0"), .package(url: "https://github.com/bufbuild/connect-swift", exact: "1.0.0"), .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.4.3"), .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "3.0.1"), @@ -29,7 +28,6 @@ let package = Package( name: "XMTPiOS", dependencies: [ .product(name: "secp256k1", package: "swift-secp256k1"), - .product(name: "Gzip", package: "GzipSwift"), .product(name: "Connect", package: "connect-swift"), .product(name: "LibXMTP", package: "libxmtp-swift"), .product(name: "CryptoSwift", package: "CryptoSwift") diff --git a/Sources/XMTPiOS/Codecs/ContentCodec.swift b/Sources/XMTPiOS/Codecs/ContentCodec.swift index e715a191..c4d94556 100644 --- a/Sources/XMTPiOS/Codecs/ContentCodec.swift +++ b/Sources/XMTPiOS/Codecs/ContentCodec.swift @@ -40,7 +40,11 @@ extension EncodedContent { copy.compression = .gzip } - copy.content = try compression.compress(content: content) + if let compressedContent = compression.compress(content: content) { + copy.content = compressedContent + } else { + throw CodecError.invalidContent + } return copy } @@ -54,15 +58,24 @@ extension EncodedContent { switch compression { case .gzip: - copy.content = try EncodedContentCompression.gzip.decompress(content: content) + if let decompressedContent = EncodedContentCompression.gzip.decompress(content: content) { + copy.content = decompressedContent + } else { + throw CodecError.invalidContent + } case .deflate: - copy.content = try EncodedContentCompression.deflate.decompress(content: content) + if let decompressedContent = EncodedContentCompression.deflate.decompress(content: content) { + copy.content = decompressedContent + } else { + throw CodecError.invalidContent + } default: return copy } return copy } + } public protocol ContentCodec: Hashable, Equatable { diff --git a/Sources/XMTPiOS/EncodedContentCompression.swift b/Sources/XMTPiOS/EncodedContentCompression.swift index 1cb45c0c..59fe668a 100644 --- a/Sources/XMTPiOS/EncodedContentCompression.swift +++ b/Sources/XMTPiOS/EncodedContentCompression.swift @@ -1,60 +1,63 @@ -// -// EncodedContentCompression.swift -// -// -// Created by Pat Nakajima on 1/19/23. -// - +import Compression import Foundation -import Gzip -import zlib public enum EncodedContentCompression { - case deflate, gzip + case deflate + case gzip - func compress(content: Data) throws -> Data { + func compress(content: Data) -> Data? { switch self { case .deflate: - // 78 9C - Default Compression according to https://www.ietf.org/rfc/rfc1950.txt - let header = Data([0x78, 0x9C]) - - // Perform rfc1951 compression - let compressed = try (content as NSData).compressed(using: .zlib) as Data - - // Needed for rfc1950 compliance - let checksum = adler32(content) - - return header + compressed + checksum + return compressData(content, using: COMPRESSION_ZLIB) case .gzip: - return try content.gzipped() + return compressData(content, using: COMPRESSION_LZFSE) // For GZIP, switch to COMPRESSION_ZLIB if needed. } } - func decompress(content: Data) throws -> Data { + func decompress(content: Data) -> Data? { switch self { case .deflate: - // Swift uses https://www.ietf.org/rfc/rfc1951.txt while JS uses https://www.ietf.org/rfc/rfc1950.txt - // They're basically the same except the JS version has a two byte header that we can just get rid of - // and a four byte checksum at the end that seems to be ignored here. - let data = NSData(data: content[2...]) - let inflated = try data.decompressed(using: .zlib) - return inflated as Data + return decompressData(content, using: COMPRESSION_ZLIB) case .gzip: - return try content.gunzipped() + return decompressData(content, using: COMPRESSION_LZFSE) // For GZIP, switch to COMPRESSION_ZLIB if needed. } } - private func adler32(_ data: Data) -> Data { - let prime = UInt32(65521) - var s1 = UInt32(1 & 0xFFFF) - var s2 = UInt32((1 >> 16) & 0xFFFF) - data.forEach { - s1 += UInt32($0) - if s1 >= prime { s1 = s1 % prime } - s2 += s1 - if s2 >= prime { s2 = s2 % prime } + // Helper method to compress data using the Compression framework + private func compressData( + _ data: Data, using algorithm: compression_algorithm + ) -> Data? { + let destinationBuffer = UnsafeMutablePointer.allocate( + capacity: data.count) + defer { destinationBuffer.deallocate() } + + let compressedSize = data.withUnsafeBytes { sourceBuffer in + compression_encode_buffer( + destinationBuffer, data.count, + sourceBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + data.count, nil, algorithm) } - var result = ((s2 << 16) | s1).bigEndian - return Data(bytes: &result, count: MemoryLayout.size) + + guard compressedSize > 0 else { return nil } + return Data(bytes: destinationBuffer, count: compressedSize) + } + + // Helper method to decompress data using the Compression framework + private func decompressData( + _ data: Data, using algorithm: compression_algorithm + ) -> Data? { + let destinationBuffer = UnsafeMutablePointer.allocate( + capacity: data.count * 4) // Allocate enough memory for decompressed data + defer { destinationBuffer.deallocate() } + + let decompressedSize = data.withUnsafeBytes { sourceBuffer in + compression_decode_buffer( + destinationBuffer, data.count * 4, + sourceBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self), + data.count, nil, algorithm) + } + + guard decompressedSize > 0 else { return nil } + return Data(bytes: destinationBuffer, count: decompressedSize) } } diff --git a/XMTP.podspec b/XMTP.podspec index 33dc54a4..0f7c2449 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |spec| spec.license = "MIT" spec.author = { "XMTP" => "eng@xmtp.com" } - spec.platform = :ios, '14.0', :macos, '11.0' + spec.platform = :ios, '14.0', :macos, '11.0' spec.swift_version = '5.3' @@ -20,7 +20,6 @@ Pod::Spec.new do |spec| spec.source_files = "Sources/**/*.swift" spec.frameworks = "CryptoKit", "UIKit" - spec.dependency "GzipSwift" spec.dependency "Connect-Swift", "= 1.0.0" spec.dependency 'LibXMTP', '= 3.0.1' spec.dependency 'CryptoSwift', '= 1.8.3' diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.pbxproj b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.pbxproj index 67a5a39e..1222d193 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.pbxproj +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ A6C0F3862AC1E549008C6AA7 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C0F3852AC1E549008C6AA7 /* Data.swift */; }; A6D192D0293A7B97006B49F2 /* ConversationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D192CF293A7B97006B49F2 /* ConversationListView.swift */; }; E513AEA32CE6AF2700BC31C3 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E513AEA22CE6AF2700BC31C3 /* CryptoSwift */; }; + E58362512CEBC4A6003D5D00 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = E58362502CEBC4A6003D5D00 /* GZIP */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -86,6 +87,7 @@ E513AEA32CE6AF2700BC31C3 /* CryptoSwift in Frameworks */, A6606A1A2B5EE80000E2ED4D /* XMTPiOS in Frameworks */, A6C0F37B2AC1E321008C6AA7 /* Starscream in Frameworks */, + E58362512CEBC4A6003D5D00 /* GZIP in Frameworks */, A65F070A297B5E8600C3C76E /* KeychainAccess in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -204,6 +206,7 @@ A6C0F37A2AC1E321008C6AA7 /* Starscream */, A6606A192B5EE80000E2ED4D /* XMTPiOS */, E513AEA22CE6AF2700BC31C3 /* CryptoSwift */, + E58362502CEBC4A6003D5D00 /* GZIP */, ); productName = XMTPiOSExample; productReference = A628198F292DC825004B9117 /* XMTPiOSExample.app */; @@ -238,6 +241,7 @@ A6C0F3792AC1E321008C6AA7 /* XCRemoteSwiftPackageReference "Starscream" */, E513AEA12CE6AF2700BC31C3 /* XCRemoteSwiftPackageReference "CryptoSwift" */, E5BCC0952CEBA58F00E11814 /* XCRemoteSwiftPackageReference "swift-secp256k1" */, + E583624F2CEBC4A6003D5D00 /* XCRemoteSwiftPackageReference "GZIP" */, ); productRefGroup = A6281990292DC825004B9117 /* Products */; projectDirPath = ""; @@ -552,6 +556,14 @@ minimumVersion = 1.8.3; }; }; + E583624F2CEBC4A6003D5D00 /* XCRemoteSwiftPackageReference "GZIP" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/nicklockwood/GZIP"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.2; + }; + }; E5BCC0952CEBA58F00E11814 /* XCRemoteSwiftPackageReference "swift-secp256k1" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/21-DOT-DEV/swift-secp256k1.git"; @@ -582,6 +594,11 @@ package = E513AEA12CE6AF2700BC31C3 /* XCRemoteSwiftPackageReference "CryptoSwift" */; productName = CryptoSwift; }; + E58362502CEBC4A6003D5D00 /* GZIP */ = { + isa = XCSwiftPackageProductDependency; + package = E583624F2CEBC4A6003D5D00 /* XCRemoteSwiftPackageReference "GZIP" */; + productName = GZIP; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = A6281987292DC825004B9117 /* Project object */; diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index aaf82bde..fb13ced0 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,12 +19,12 @@ } }, { - "identity" : "gzipswift", + "identity" : "gzip", "kind" : "remoteSourceControl", - "location" : "https://github.com/1024jp/GzipSwift", + "location" : "https://github.com/nicklockwood/GZIP", "state" : { - "revision" : "56bf51fdd2fe4b2cf254b2cf34aede3d7caccc6c", - "version" : "6.1.0" + "revision" : "f710a37aa978a93b815a4f64bd504dc4c3256312", + "version" : "1.3.2" } }, {