-
Notifications
You must be signed in to change notification settings - Fork 0
iOS: add CodePushBinaryDiffPatcher to apply bsdiff patches #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #import <Foundation/Foundation.h> | ||
| #import "CodePushDiffManifest.h" | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface CodePushBinaryDiffPatcher : NSObject | ||
|
|
||
| // Applies every entry in manifest.patchedFiles: verifies the pre-patch file | ||
| // against baseHash, applies the patch into newUpdateFolder, then verifies | ||
| // the result against targetHash. | ||
| // | ||
| // All three folder arguments must exist on disk. Files within them need not, | ||
| // the patch output and its parent directories are created as needed. | ||
| // | ||
| // Returns NO and sets *error on the first failure. | ||
| + (BOOL)applyBinaryDiffPatchesFromManifest:(CodePushDiffManifest *)manifest | ||
| currentPackageFolder:(NSString *)currentPackageFolder | ||
| unzippedFolder:(NSString *)unzippedFolder | ||
| newUpdateFolder:(NSString *)newUpdateFolder | ||
| error:(NSError **)error | ||
| NS_SWIFT_NAME(applyBinaryDiffPatches(manifest:currentPackageFolder:unzippedFolder:newUpdateFolder:)); | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| #import "CodePushBinaryDiffPatcher.h" | ||
| #import "CodePushErrorUtils.h" | ||
| #import "CodePushSha256.h" | ||
| #import "bspatch_bridge.h" | ||
|
|
||
| static NSError *patchApplyError(NSString *relativePath, NSString *reason) | ||
| { | ||
| return [CodePushErrorUtils errorWithMessage: | ||
| [NSString stringWithFormat:@"Failed to apply binary diff patch for \"%@\": %@", relativePath, reason]]; | ||
| } | ||
|
|
||
| // Keep in sync with shared/diffpatch/bspatch_bridge.h. | ||
| static NSString *describeBSPatchResult(CodePushBSPatchResult result) | ||
| { | ||
| switch (result) { | ||
| case CODEPUSH_BSPATCH_OK: return @"OK"; | ||
| case CODEPUSH_BSPATCH_ERR_BAD_DIFF_HEADER: return @"BAD_DIFF_HEADER"; | ||
| case CODEPUSH_BSPATCH_ERR_OPEN_OLD: return @"OPEN_OLD_FAILED"; | ||
| case CODEPUSH_BSPATCH_ERR_OPEN_DIFF: return @"OPEN_DIFF_FAILED"; | ||
| case CODEPUSH_BSPATCH_ERR_OPEN_OUT: return @"OPEN_OUT_FAILED"; | ||
| case CODEPUSH_BSPATCH_ERR_OOM: return @"OUT_OF_MEMORY"; | ||
| case CODEPUSH_BSPATCH_ERR_PATCH_FAILED: return @"PATCH_FAILED"; | ||
| } | ||
| return [NSString stringWithFormat:@"UNKNOWN (%ld)", (long)result]; | ||
| } | ||
|
|
||
| static NSString *resolveWithin(NSString *base, NSString *path, NSString *manifestEntry, NSError **error) | ||
| { | ||
| NSError *resolveError = nil; | ||
| NSString *resolved = [CodePushDiffManifest resolvePath:path withinFolder:base error:&resolveError]; | ||
| if (resolved == nil && error) { | ||
| *error = patchApplyError(manifestEntry, resolveError.localizedDescription); | ||
| } | ||
| return resolved; | ||
| } | ||
|
Comment on lines
+27
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This maps every
Two of those are manifest-triggerable: Worth noting this is an iOS-only divergence. Android's Threading the reason out of
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to fix this by adopting the standard ObjC pattern of a PR #58 is updated with the change to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good choice, thanks! |
||
|
|
||
| @implementation CodePushBinaryDiffPatcher | ||
|
|
||
| + (BOOL)applyBinaryDiffPatchesFromManifest:(CodePushDiffManifest *)manifest | ||
| currentPackageFolder:(NSString *)currentPackageFolder | ||
| unzippedFolder:(NSString *)unzippedFolder | ||
| newUpdateFolder:(NSString *)newUpdateFolder | ||
| error:(NSError **)error | ||
| { | ||
| NSDictionary<NSString *, CodePushPatchedFileEntry *> *patchedFiles = manifest.patchedFiles; | ||
|
|
||
| for (NSString *relativePath in patchedFiles) { | ||
| CodePushPatchedFileEntry *entry = patchedFiles[relativePath]; | ||
| if (![entry.algo isEqualToString:@"bsdiff"]) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"unsupported patch algorithm: %@", entry.algo]); | ||
| return NO; | ||
| } | ||
| } | ||
|
|
||
| NSFileManager *fileManager = [NSFileManager defaultManager]; | ||
|
|
||
| for (NSString *relativePath in patchedFiles) { | ||
| CodePushPatchedFileEntry *entry = patchedFiles[relativePath]; | ||
|
|
||
| NSString *oldFile = resolveWithin(currentPackageFolder, relativePath, relativePath, error); | ||
| if (!oldFile) return NO; | ||
|
|
||
| NSError *oldFileHashError = nil; | ||
| NSString *oldFileHash = CodePushSha256HexForFile(oldFile, &oldFileHashError); | ||
| if (!oldFileHash) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"could not hash the file to patch: %@", oldFileHashError.localizedDescription]); | ||
| return NO; | ||
| } | ||
| if (![oldFileHash isEqualToString:entry.baseHash]) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"baseHash mismatch: expected %@, got %@", entry.baseHash, oldFileHash]); | ||
| return NO; | ||
| } | ||
|
|
||
| NSString *diffFile = resolveWithin(unzippedFolder, entry.patch, relativePath, error); | ||
| if (!diffFile) return NO; | ||
|
|
||
| NSString *newFile = resolveWithin(newUpdateFolder, relativePath, relativePath, error); | ||
| if (!newFile) return NO; | ||
|
|
||
| NSError *createDirError = nil; | ||
| [fileManager createDirectoryAtPath:[newFile stringByDeletingLastPathComponent] | ||
| withIntermediateDirectories:YES | ||
| attributes:nil | ||
| error:&createDirError]; | ||
| if (createDirError) { | ||
| if (error) *error = patchApplyError(relativePath, createDirError.localizedDescription); | ||
| return NO; | ||
| } | ||
|
|
||
| CodePushBSPatchResult result = codepush_bspatch_apply(oldFile.fileSystemRepresentation, | ||
| diffFile.fileSystemRepresentation, | ||
| newFile.fileSystemRepresentation); | ||
| if (result != CODEPUSH_BSPATCH_OK) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"patch failed: %@", describeBSPatchResult(result)]); | ||
| return NO; | ||
| } | ||
|
|
||
| NSError *newFileHashError = nil; | ||
| NSString *newFileHash = CodePushSha256HexForFile(newFile, &newFileHashError); | ||
| if (!newFileHash) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"could not hash the patched file: %@", newFileHashError.localizedDescription]); | ||
| return NO; | ||
| } | ||
| if (![newFileHash isEqualToString:entry.targetHash]) { | ||
| if (error) *error = patchApplyError(relativePath, [NSString stringWithFormat:@"targetHash mismatch: expected %@, got %@", entry.targetHash, newFileHash]); | ||
| return NO; | ||
| } | ||
| } | ||
|
|
||
| return YES; | ||
| } | ||
|
|
||
| @end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These three entries add
CodePushBinaryDiffPatcher.mtoCodePush,CodePush-tvOSandCodePushTests— butshared/diffpatch/bspatch_bridge.c(and the hdiffpatch sources) are not in the tvOS target, and the patcher'scodepush_bspatch_applycall atCodePushBinaryDiffPatcher.m:89is its only caller. I parsed both trees to confirm:So
_codepush_bspatch_applybecomes unresolved inCodePush-tvOS, and it wasn't before this PR.Two things keep this from being a build break, and I want to be precise about them rather than overstate it:
CodePush-tvOSis a static library, so unresolved symbols are legal in the archive andxcodebuildwill not fail on that target. And CocoaPods consumers are unaffected — the podspec compilesshared/diffpatch/*.cand the hdiffpatch sources for tvOS as well. It surfaces at app link time only for someone integrating tvOS manually from the.xcodeproj.Separately and pre-existing, so not yours to fix here:
_CodePushSha256HexForFileis also unresolved in that target —CodePushUpdateUtils.malready referenced it on the base branch withCodePushSha256.mabsent. The patcher just becomes a second caller.Either drop the patcher from
CodePush-tvOS, or addbspatch_bridge.c+ the hdiffpatch sources (andCodePushSha256.m) to it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
tvOStarget frequently gets flagged, and we should clean it up one day, I just want to better understand what was its original purpose. It's been broken for years, we don't really use it in CI anyway, and it doesn't get distributed to users (Cocoapods is the distribution mechanism). I think it's fine for now.