From 1d1f651a2502f4525616dd6055043ea74b5d4773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Tue, 25 Aug 2026 17:49:32 +0200 Subject: [PATCH] iOS: consolidate SHA-256 hashing into a shared utility --- ios/CodePush.xcodeproj/project.pbxproj | 22 ++++-- ios/CodePush/CodePushSha256.h | 12 ++++ ios/CodePush/CodePushSha256.m | 61 ++++++++++++++++ ios/CodePush/CodePushUpdateUtils.m | 40 +++++------ ios/CodePushTests/CodePushSha256Tests.swift | 69 +++++++++++++++++++ .../CodePushTests-Bridging-Header.h | 1 + 6 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 ios/CodePush/CodePushSha256.h create mode 100644 ios/CodePush/CodePushSha256.m create mode 100644 ios/CodePushTests/CodePushSha256Tests.swift diff --git a/ios/CodePush.xcodeproj/project.pbxproj b/ios/CodePush.xcodeproj/project.pbxproj index 958a0d6f..a2c855b7 100644 --- a/ios/CodePush.xcodeproj/project.pbxproj +++ b/ios/CodePush.xcodeproj/project.pbxproj @@ -7,11 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 0ABCB5DEFE01A7A15552A498 /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; 08B8B3B8260E70B7ECA85451 /* bspatch_bridge.c in Sources */ = {isa = PBXBuildFile; fileRef = A430CBE260F09A3233110E28 /* bspatch_bridge.c */; }; - A75C1A7A555663186E25AA3D /* libHDiffPatch/HPatch/patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */; }; - D8C294C1D2625E79BC302CCC /* bsdiff_wrapper/bspatch_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */; }; - 78E9CECCB820DC4BA09BF09A /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; + 0ABCB5DEFE01A7A15552A498 /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; + 0EC08441DDDD08C96DDE851C /* CodePushSha256Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23322E9E8EFBE01F1FACFB1 /* CodePushSha256Tests.swift */; }; 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; }; 1B762E901C9A5E9A006EF800 /* CodePushErrorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B762E8F1C9A5E9A006EF800 /* CodePushErrorUtils.m */; }; @@ -83,6 +81,7 @@ 3221E4912C8ABE1400268379 /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 3221E44F2C8ABE1300268379 /* SSZipArchive.m */; }; 3221E4922C8ABE1400268379 /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 3221E44F2C8ABE1300268379 /* SSZipArchive.m */; }; 47F66D5AF3C3185E1A3E3B15 /* bspatch_bridge.c in Sources */ = {isa = PBXBuildFile; fileRef = A430CBE260F09A3233110E28 /* bspatch_bridge.c */; }; + 537831E09276DCD4475752F3 /* CodePushSha256.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F67DC39D49BF136E1C7F041 /* CodePushSha256.m */; }; 540D20121C7684FE00D6EF41 /* CodePushUpdateUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */; }; 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */; }; 5498D8F61D21F14100B5EB43 /* CodePushUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5498D8F51D21F14100B5EB43 /* CodePushUtils.m */; }; @@ -100,13 +99,17 @@ 6463C8361EBA0CFB0095B8CD /* RCTConvert+CodePushUpdateState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BCC09A61CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m */; }; 6463C8451EBA0D1F0095B8CD /* CodePush.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; 6463C8471EBA0D290095B8CD /* CodePush.h in Headers */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; + 78E9CECCB820DC4BA09BF09A /* file_for_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 70779807AB59EA4711737F4E /* file_for_patch.c */; }; 810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; }; 81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; }; 8482F84C1E24C58300F793DB /* CodePush.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; 8482F84E1E24C66300F793DB /* CodePush.h in Headers */ = {isa = PBXBuildFile; fileRef = 13BE3DEC1AC21097009241FE /* CodePush.h */; }; + A75C1A7A555663186E25AA3D /* libHDiffPatch/HPatch/patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */; }; A88F11124A2120A8373A8B61 /* bsdiff_wrapper/bspatch_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */; }; B5B50F91FAAF80444988E284 /* BSPatchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */; }; + D8C294C1D2625E79BC302CCC /* bsdiff_wrapper/bspatch_wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 0DC7989C75C72A774EF3685F /* bsdiff_wrapper/bspatch_wrapper.c */; }; DC983F1C71E0E7131BB343C5 /* libHDiffPatch/HPatch/patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */; }; + F0CE653CAB93F38FC0C0CAF8 /* CodePushSha256.h in Headers */ = {isa = PBXBuildFile; fileRef = 73A9DD75BE69E8E3A8C3548A /* CodePushSha256.h */; }; F85736761F4F03BF00C9C00A /* MF_Base64Additions.h in Headers */ = {isa = PBXBuildFile; fileRef = F85736731F4F03BF00C9C00A /* MF_Base64Additions.h */; }; F85736771F4F03BF00C9C00A /* MF_Base64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = F85736741F4F03BF00C9C00A /* MF_Base64Additions.m */; }; F886644D1F4AD1EE0036D01B /* JWTAlgorithm.h in Headers */ = {isa = PBXBuildFile; fileRef = F88664151F4AD1EE0036D01B /* JWTAlgorithm.h */; }; @@ -155,6 +158,7 @@ F88664781F4AD1EE0036D01B /* JWTDeprecations.h in Headers */ = {isa = PBXBuildFile; fileRef = F886644A1F4AD1EE0036D01B /* JWTDeprecations.h */; }; F88664791F4AD1EE0036D01B /* JWTErrorDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = F886644B1F4AD1EE0036D01B /* JWTErrorDescription.h */; }; F886647A1F4AD1EF0036D01B /* JWTErrorDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = F886644C1F4AD1EE0036D01B /* JWTErrorDescription.m */; }; + FD00FBDD4A5916948AAC735D /* CodePushSha256.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F67DC39D49BF136E1C7F041 /* CodePushSha256.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -231,12 +235,15 @@ 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = CodePush/CodePushTelemetryManager.m; sourceTree = ""; }; 5498D8F51D21F14100B5EB43 /* CodePushUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUtils.m; path = CodePush/CodePushUtils.m; sourceTree = ""; }; 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushDownloadHandler.m; path = CodePush/CodePushDownloadHandler.m; sourceTree = ""; }; + 6F67DC39D49BF136E1C7F041 /* CodePushSha256.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = CodePushSha256.m; path = CodePush/CodePushSha256.m; sourceTree = ""; }; 70779807AB59EA4711737F4E /* file_for_patch.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = file_for_patch.c; sourceTree = ""; }; + 73A9DD75BE69E8E3A8C3548A /* CodePushSha256.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = CodePushSha256.h; path = CodePush/CodePushSha256.h; sourceTree = ""; }; 810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushPackage.m; path = CodePush/CodePushPackage.m; sourceTree = ""; }; 81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushConfig.m; path = CodePush/CodePushConfig.m; sourceTree = ""; }; 827016DF6E52E356F4B89D18 /* libHDiffPatch/HPatch/patch.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = libHDiffPatch/HPatch/patch.c; sourceTree = ""; }; 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BSPatchTests.swift; sourceTree = ""; }; A430CBE260F09A3233110E28 /* bspatch_bridge.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = bspatch_bridge.c; sourceTree = ""; }; + D23322E9E8EFBE01F1FACFB1 /* CodePushSha256Tests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CodePushSha256Tests.swift; sourceTree = ""; }; E74D80ECD8DD03C4C0772B7B /* Fixtures */ = {isa = PBXFileReference; explicitFileType = folder; includeInIndex = 0; path = Fixtures; sourceTree = ""; }; E9FA144425AE78B97AD6C870 /* CodePushTests-Bridging-Header.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "CodePushTests-Bridging-Header.h"; sourceTree = ""; }; F85736731F4F03BF00C9C00A /* MF_Base64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MF_Base64Additions.h; sourceTree = ""; }; @@ -442,6 +449,8 @@ F886647B1F4ADB500036D01B /* libCodePush.a */, 2EB4092D590839720984EE01 /* RepoRoot */, C283376E6F60830C5CB41994 /* CodePushTests */, + 73A9DD75BE69E8E3A8C3548A /* CodePushSha256.h */, + 6F67DC39D49BF136E1C7F041 /* CodePushSha256.m */, ); sourceTree = ""; }; @@ -461,6 +470,7 @@ 95D6DD0EACAC8D095880DFD0 /* BSPatchTests.swift */, E9FA144425AE78B97AD6C870 /* CodePushTests-Bridging-Header.h */, E74D80ECD8DD03C4C0772B7B /* Fixtures */, + D23322E9E8EFBE01F1FACFB1 /* CodePushSha256Tests.swift */, ); path = CodePushTests; sourceTree = ""; @@ -720,6 +730,7 @@ F886646B1F4AD1EE0036D01B /* JWTCoding+ResultTypes.h in Headers */, 3221E4652C8ABE1300268379 /* mz_strm_buf.h in Headers */, 3221E47B2C8ABE1400268379 /* mz_zip.h in Headers */, + F0CE653CAB93F38FC0C0CAF8 /* CodePushSha256.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -888,6 +899,7 @@ F88664531F4AD1EE0036D01B /* JWTAlgorithmESBase.m in Sources */, 3221E4532C8ABE1300268379 /* mz_strm_os_posix.c in Sources */, F88664721F4AD1EE0036D01B /* JWTCoding+VersionTwo.m in Sources */, + 537831E09276DCD4475752F3 /* CodePushSha256.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -933,6 +945,8 @@ DC983F1C71E0E7131BB343C5 /* libHDiffPatch/HPatch/patch.c in Sources */, A88F11124A2120A8373A8B61 /* bsdiff_wrapper/bspatch_wrapper.c in Sources */, 0ABCB5DEFE01A7A15552A498 /* file_for_patch.c in Sources */, + FD00FBDD4A5916948AAC735D /* CodePushSha256.m in Sources */, + 0EC08441DDDD08C96DDE851C /* CodePushSha256Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/CodePush/CodePushSha256.h b/ios/CodePush/CodePushSha256.h new file mode 100644 index 00000000..dab213b9 --- /dev/null +++ b/ios/CodePush/CodePushSha256.h @@ -0,0 +1,12 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +// SHA-256 hex digest of a file's contents. +// Returns nil and sets *error if the file can't be opened/read. +NSString * _Nullable CodePushSha256HexForFile(NSString *filePath, NSError **error); + +// SHA-256 hex digest of an in-memory buffer. +NSString *CodePushSha256HexForData(NSData *data); + +NS_ASSUME_NONNULL_END diff --git a/ios/CodePush/CodePushSha256.m b/ios/CodePush/CodePushSha256.m new file mode 100644 index 00000000..3003c5b7 --- /dev/null +++ b/ios/CodePush/CodePushSha256.m @@ -0,0 +1,61 @@ +#import "CodePushSha256.h" +#include + +static NSString *const CodePushSha256ErrorDomain = @"CodePushSha256Error"; + +static NSString *hexStringForDigest(unsigned char digest[CC_SHA256_DIGEST_LENGTH]) +{ + NSMutableString *hex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hex appendFormat:@"%02x", digest[i]]; + } + return hex; +} + +NSString *CodePushSha256HexForData(NSData *data) +{ + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); + return hexStringForDigest(digest); +} + +NSString *CodePushSha256HexForFile(NSString *filePath, NSError **error) +{ + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; + if (!fileHandle) { + if (error) { + *error = [NSError errorWithDomain:CodePushSha256ErrorDomain + code:1 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not open file for reading: %@", filePath] }]; + } + return nil; + } + + CC_SHA256_CTX context; + CC_SHA256_Init(&context); + + static const NSUInteger kChunkSize = 1024 * 8; + NSError *readError; + while (YES) { + NSData *chunk = [fileHandle readDataUpToLength:kChunkSize error:&readError]; + if (readError) { + [fileHandle closeFile]; + if (error) { + *error = [NSError errorWithDomain:CodePushSha256ErrorDomain + code:2 + userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not read file: %@", filePath], + NSUnderlyingErrorKey: readError }]; + } + return nil; + } + if (chunk.length == 0) { + break; + } + CC_SHA256_Update(&context, chunk.bytes, (CC_LONG)chunk.length); + } + [fileHandle closeFile]; + + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256_Final(digest, &context); + return hexStringForDigest(digest); +} diff --git a/ios/CodePush/CodePushUpdateUtils.m b/ios/CodePush/CodePushUpdateUtils.m index e0f170b7..ceada205 100644 --- a/ios/CodePush/CodePushUpdateUtils.m +++ b/ios/CodePush/CodePushUpdateUtils.m @@ -1,5 +1,5 @@ #import "CodePush.h" -#include +#import "CodePushSha256.h" #import "JWT.h" @implementation CodePushUpdateUtils @@ -57,8 +57,10 @@ + (BOOL)addContentsOfFolderToManifest:(NSString *)folderPath return NO; } } else { - NSData *fileContents = [NSData dataWithContentsOfFile:fullFilePath]; - NSString *fileContentsHash = [self computeHashForData:fileContents]; + NSString *fileContentsHash = CodePushSha256HexForFile(fullFilePath, error); + if (!fileContentsHash) { + return NO; + } [manifest addObject:[[relativePath stringByAppendingString:@":"] stringByAppendingString:fileContentsHash]]; } } @@ -66,14 +68,18 @@ + (BOOL)addContentsOfFolderToManifest:(NSString *)folderPath return YES; } -+ (void)addFileToManifest:(NSURL *)fileURL ++ (BOOL)addFileToManifest:(NSURL *)fileURL manifest:(NSMutableArray *)manifest + error:(NSError **)error { if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { - NSData *fileContents = [NSData dataWithContentsOfURL:fileURL]; - NSString *fileContentsHash = [self computeHashForData:fileContents]; + NSString *fileContentsHash = CodePushSha256HexForFile([fileURL path], error); + if (!fileContentsHash) { + return NO; + } [manifest addObject:[NSString stringWithFormat:@"%@/%@:%@", [self manifestFolderPrefix], [fileURL lastPathComponent], fileContentsHash]]; } + return YES; } + (NSString *)computeFinalHashFromManifest:(NSMutableArray *)manifest @@ -93,19 +99,7 @@ + (NSString *)computeFinalHashFromManifest:(NSMutableArray *)manifest // The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png" manifestString = [manifestString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; - return [self computeHashForData:[NSData dataWithBytes:manifestString.UTF8String length:[manifestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]]; -} - -+ (NSString *)computeHashForData:(NSData *)inputData -{ - uint8_t digest[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(inputData.bytes, (CC_LONG)inputData.length, digest); - NSMutableString* inputHash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { - [inputHash appendFormat:@"%02x", digest[i]]; - } - - return inputHash; + return CodePushSha256HexForData([NSData dataWithBytes:manifestString.UTF8String length:[manifestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]); } + (BOOL)copyEntriesInFolder:(NSString *)sourceFolder @@ -230,8 +224,12 @@ + (NSString *)getHashForBinaryContents:(NSURL *)binaryBundleUrl } } - [self addFileToManifest:binaryBundleUrl manifest:manifest]; - [self addFileToManifest:[binaryBundleUrl URLByAppendingPathExtension:@"meta"] manifest:manifest]; + if (![self addFileToManifest:binaryBundleUrl manifest:manifest error:error]) { + return nil; + } + if (![self addFileToManifest:[binaryBundleUrl URLByAppendingPathExtension:@"meta"] manifest:manifest error:error]) { + return nil; + } binaryHash = [self computeFinalHashFromManifest:manifest error:error]; diff --git a/ios/CodePushTests/CodePushSha256Tests.swift b/ios/CodePushTests/CodePushSha256Tests.swift new file mode 100644 index 00000000..60a72f21 --- /dev/null +++ b/ios/CodePushTests/CodePushSha256Tests.swift @@ -0,0 +1,69 @@ +import XCTest +import CryptoKit + +final class CodePushSha256Tests: XCTestCase { + + private var tempDir: URL! + + override func setUpWithError() throws { + tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + } + + override func tearDownWithError() throws { + try? FileManager.default.removeItem(at: tempDir) + } + + private func writeFile(named name: String, contents: Data) throws -> URL { + let url = tempDir.appendingPathComponent(name) + try contents.write(to: url) + return url + } + + func testHexForFile_matchesExpectedDigest() throws { + let url = try writeFile(named: "abc.dat", contents: Data("abc".utf8)) + + var error: NSError? + let hex = CodePushSha256HexForFile(url.path, &error) + + XCTAssertNil(error) + XCTAssertEqual(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") + } + + func testHexForFile_emptyFile_matchesEmptyStringDigest() throws { + let url = try writeFile(named: "empty.dat", contents: Data()) + + var error: NSError? + let hex = CodePushSha256HexForFile(url.path, &error) + + XCTAssertNil(error) + XCTAssertEqual(hex, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + } + + func testHexForFile_largerThanChunkSize_matchesExpectedDigest() throws { + // Deterministic payload larger than the implementation's 8 KiB read-chunk + // boundary, so this exercises the multi-chunk CC_SHA256_Update loop rather + // than only its first iteration. + let payload = Data((0..<(8 * 1024 + 173)).map { UInt8($0 % 256) }) + let url = try writeFile(named: "large.dat", contents: payload) + let expectedHex = SHA256.hash(data: payload) + .map { String(format: "%02x", $0) } + .joined() + + var error: NSError? + let hex = CodePushSha256HexForFile(url.path, &error) + + XCTAssertNil(error) + XCTAssertEqual(hex, expectedHex) + } + + func testHexForFile_missingFile_returnsNilAndSetsError() { + let missingURL = tempDir.appendingPathComponent("does_not_exist.dat") + + var error: NSError? + let hex = CodePushSha256HexForFile(missingURL.path, &error) + + XCTAssertNil(hex) + XCTAssertNotNil(error) + } +} diff --git a/ios/CodePushTests/CodePushTests-Bridging-Header.h b/ios/CodePushTests/CodePushTests-Bridging-Header.h index 43382433..c8ac5747 100644 --- a/ios/CodePushTests/CodePushTests-Bridging-Header.h +++ b/ios/CodePushTests/CodePushTests-Bridging-Header.h @@ -1 +1,2 @@ #import "bspatch_bridge.h" +#import "CodePushSha256.h"