@hot-updater/react-native 0.30.12 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HotUpdater.podspec +1 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/hotupdater/BsdiffPatch.kt +284 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +885 -5
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +13 -2
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +37 -8
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +48 -12
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +60 -22
- package/android/src/newarch/HotUpdater.kt +14 -2
- package/android/src/newarch/HotUpdaterModule.kt +112 -1
- package/android/src/oldarch/HotUpdater.kt +14 -2
- package/android/src/oldarch/HotUpdaterModule.kt +107 -3
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +46 -0
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +29 -1
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -1
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.h +16 -0
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.mm +343 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1093 -47
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +2 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +12 -2
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +64 -5
- package/ios/HotUpdater/Internal/NotificationExtension.swift +2 -1
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +28 -5
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +11 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +11 -5
- package/lib/commonjs/DefaultResolver.js +6 -2
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/DefaultResolver.spec.js +79 -3
- package/lib/commonjs/DefaultResolver.spec.js.map +1 -1
- package/lib/commonjs/checkForUpdate.js +6 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +53 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +117 -1
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +40 -16
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +119 -3
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +8 -0
- package/lib/commonjs/sdkVersion.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +72 -5
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/store.spec.js +265 -8
- package/lib/commonjs/store.spec.js.map +1 -1
- package/lib/commonjs/wrap.js +19 -5
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +74 -0
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/DefaultResolver.js +6 -2
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/DefaultResolver.spec.js +78 -3
- package/lib/module/DefaultResolver.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +6 -0
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +53 -18
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +117 -1
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +40 -16
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +119 -3
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +4 -0
- package/lib/module/sdkVersion.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +72 -5
- package/lib/module/store.js.map +1 -1
- package/lib/module/store.spec.js +265 -8
- package/lib/module/store.spec.js.map +1 -1
- package/lib/module/wrap.js +19 -5
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +74 -0
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +36 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +56 -3
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +2 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +5 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +43 -29
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +36 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +56 -3
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +2 -0
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +5 -0
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +43 -29
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +20 -10
- package/src/DefaultResolver.spec.ts +103 -3
- package/src/DefaultResolver.ts +9 -2
- package/src/checkForUpdate.ts +9 -2
- package/src/index.spec.ts +149 -1
- package/src/index.ts +99 -40
- package/src/native.spec.ts +139 -1
- package/src/native.ts +145 -31
- package/src/sdkVersion.ts +1 -0
- package/src/specs/NativeHotUpdater.ts +28 -0
- package/src/store.spec.ts +324 -9
- package/src/store.ts +133 -7
- package/src/wrap.spec.ts +91 -0
- package/src/wrap.tsx +79 -42
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertArrayEquals
|
|
4
|
+
import org.junit.Assert.assertFalse
|
|
5
|
+
import org.junit.Assert.assertThrows
|
|
6
|
+
import org.junit.Assert.assertTrue
|
|
7
|
+
import org.junit.Rule
|
|
8
|
+
import org.junit.Test
|
|
9
|
+
import org.junit.rules.TemporaryFolder
|
|
10
|
+
import java.util.Base64
|
|
11
|
+
|
|
12
|
+
class BsdiffPatchTest {
|
|
13
|
+
@get:Rule
|
|
14
|
+
val temporaryFolder = TemporaryFolder()
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun `applies bsdiff patch and verifies patched output hash`() {
|
|
18
|
+
val base = "console.log(\"base bundle\");\n".toByteArray()
|
|
19
|
+
val expected = "console.log(\"patched bundle\");\n".toByteArray()
|
|
20
|
+
val patch = Base64.getDecoder().decode(BSDIFF_PATCH_FIXTURE_BASE64)
|
|
21
|
+
val baseFile = temporaryFolder.newFile("base.bundle").apply { writeBytes(base) }
|
|
22
|
+
val patchFile = temporaryFolder.newFile("patch.bsdiff").apply { writeBytes(patch) }
|
|
23
|
+
val outputFile = temporaryFolder.newFile("output.bundle")
|
|
24
|
+
|
|
25
|
+
BsdiffPatch.apply(baseFile, patchFile, outputFile)
|
|
26
|
+
|
|
27
|
+
assertArrayEquals(expected, outputFile.readBytes())
|
|
28
|
+
assertTrue(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(outputFile)))
|
|
29
|
+
assertFalse(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(baseFile)))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun `rejects invalid bsdiff patch`() {
|
|
34
|
+
val base = "console.log(\"base bundle\");\n".toByteArray()
|
|
35
|
+
val invalidPatch = "not-a-bsdiff-patch-with-header".toByteArray()
|
|
36
|
+
|
|
37
|
+
assertThrows(IllegalArgumentException::class.java) {
|
|
38
|
+
BsdiffPatch.apply(base, invalidPatch)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
companion object {
|
|
43
|
+
private const val BSDIFF_PATCH_FIXTURE_BASE64 =
|
|
44
|
+
"RU5EU0xFWS9CU0RJRkY0Mx8AAAAAAAAAQlpoOTFBWSZTWb12MIEAAAB5gEQYAADQYQAIPsXOACAAIo0A0NAaNCgAGgZMgHAtYscVxxRtTt4nmaj70g4gQSF5+T4u5IpwoSF67GEC"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -231,6 +231,28 @@ class BundleFileStorageServiceTest {
|
|
|
231
231
|
assertEquals(stagingDir.name, service.getBundleId())
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
@Test
|
|
235
|
+
fun `manifest driven install is disabled before first OTA`() {
|
|
236
|
+
val rootDir = temporaryFolder.newFolder("first-ota-manifest-disabled")
|
|
237
|
+
val service = createService(rootDir)
|
|
238
|
+
|
|
239
|
+
assertFalse(invokeCanUseManifestDrivenInstall(service))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@Test
|
|
243
|
+
fun `manifest driven install is enabled for active OTA bundle with manifest`() {
|
|
244
|
+
val rootDir = temporaryFolder.newFolder("active-ota-manifest-enabled")
|
|
245
|
+
val preferences = InMemoryPreferencesService()
|
|
246
|
+
val service = createService(rootDir, preferences)
|
|
247
|
+
val activeDir = createBundleDir(rootDir, "active-bundle")
|
|
248
|
+
val activeBundleFile = writeFile(activeDir, "index.android.bundle")
|
|
249
|
+
writeManifest(activeDir, listOf("index.android.bundle"))
|
|
250
|
+
|
|
251
|
+
preferences.setItem("HotUpdaterBundleURL", activeBundleFile.absolutePath)
|
|
252
|
+
|
|
253
|
+
assertTrue(invokeCanUseManifestDrivenInstall(service))
|
|
254
|
+
}
|
|
255
|
+
|
|
234
256
|
private fun createService(
|
|
235
257
|
rootDir: File,
|
|
236
258
|
preferences: InMemoryPreferencesService = InMemoryPreferencesService(),
|
|
@@ -306,6 +328,12 @@ class BundleFileStorageServiceTest {
|
|
|
306
328
|
return method.invoke(service, bundleDir) as File?
|
|
307
329
|
}
|
|
308
330
|
|
|
331
|
+
private fun invokeCanUseManifestDrivenInstall(service: BundleFileStorageService): Boolean {
|
|
332
|
+
val method = BundleFileStorageService::class.java.getDeclaredMethod("canUseManifestDrivenInstall")
|
|
333
|
+
method.isAccessible = true
|
|
334
|
+
return method.invoke(service) as Boolean
|
|
335
|
+
}
|
|
336
|
+
|
|
309
337
|
private fun assertResolvedBundlePath(
|
|
310
338
|
service: BundleFileStorageService,
|
|
311
339
|
bundleDir: File,
|
|
@@ -368,7 +396,7 @@ class BundleFileStorageServiceTest {
|
|
|
368
396
|
fileUrl: URL,
|
|
369
397
|
destination: File,
|
|
370
398
|
fileSizeCallback: ((Long) -> Unit)?,
|
|
371
|
-
progressCallback: (
|
|
399
|
+
progressCallback: (DownloadProgress) -> Unit,
|
|
372
400
|
): DownloadResult = error("downloadFile should not be called in these tests")
|
|
373
401
|
}
|
|
374
402
|
|
|
@@ -179,7 +179,10 @@ class HotUpdaterImplTest {
|
|
|
179
179
|
bundleId: String,
|
|
180
180
|
fileUrl: String?,
|
|
181
181
|
fileHash: String?,
|
|
182
|
-
|
|
182
|
+
manifestUrl: String?,
|
|
183
|
+
manifestFileHash: String?,
|
|
184
|
+
changedAssets: Map<String, ChangedAssetDescriptor>?,
|
|
185
|
+
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
183
186
|
) = Unit
|
|
184
187
|
|
|
185
188
|
override fun markLaunchCompleted(currentBundleId: String?) = Unit
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
FOUNDATION_EXPORT BOOL HotUpdaterApplyBsdiffPatch(NSString *patchPath,
|
|
6
|
+
NSString *basePath,
|
|
7
|
+
NSString *outputPath);
|
|
8
|
+
|
|
9
|
+
@interface BsdiffPatchBridge : NSObject
|
|
10
|
+
+ (BOOL)applyPatchAtPath:(NSString *)patchPath
|
|
11
|
+
toBaseAtPath:(NSString *)basePath
|
|
12
|
+
outputAtPath:(NSString *)outputPath
|
|
13
|
+
error:(NSError * _Nullable * _Nullable)error;
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#import "BsdiffPatchBridge.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <bzlib.h>
|
|
5
|
+
#include <cerrno>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <fcntl.h>
|
|
9
|
+
#include <sys/stat.h>
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
constexpr NSUInteger kBzipChunkSize = 64 * 1024;
|
|
15
|
+
|
|
16
|
+
NSError *MakePatchError(NSString *message) {
|
|
17
|
+
return [NSError errorWithDomain:@"HotUpdater.Bsdiff"
|
|
18
|
+
code:1
|
|
19
|
+
userInfo:@{NSLocalizedDescriptionKey: message}];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
NSError *MakeSystemError(NSString *message) {
|
|
23
|
+
return MakePatchError(
|
|
24
|
+
[NSString stringWithFormat:@"%@: %s", message, strerror(errno)]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
int64_t ReadOfft(const uint8_t *bytes) {
|
|
28
|
+
uint64_t value = 0;
|
|
29
|
+
for (NSUInteger index = 0; index < 8; index += 1) {
|
|
30
|
+
value |= (static_cast<uint64_t>(bytes[index]) << (index * 8));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ((value & (1ULL << 63)) == 0) {
|
|
34
|
+
return static_cast<int64_t>(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return -static_cast<int64_t>(value & ~(1ULL << 63));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool CheckedAddInt64(int64_t left, int64_t right, int64_t *result) {
|
|
41
|
+
if ((right > 0 && left > INT64_MAX - right) ||
|
|
42
|
+
(right < 0 && left < INT64_MIN - right)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
*result = left + right;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bool ReadBzipExactly(BZFILE *bz2,
|
|
50
|
+
void *buffer,
|
|
51
|
+
size_t length,
|
|
52
|
+
NSError **error) {
|
|
53
|
+
auto *bytes = reinterpret_cast<uint8_t *>(buffer);
|
|
54
|
+
size_t offset = 0;
|
|
55
|
+
|
|
56
|
+
while (offset < length) {
|
|
57
|
+
int bz2err = BZ_OK;
|
|
58
|
+
int request = static_cast<int>(
|
|
59
|
+
std::min<size_t>(length - offset, static_cast<size_t>(INT_MAX)));
|
|
60
|
+
int read = BZ2_bzRead(&bz2err, bz2, bytes + offset, request);
|
|
61
|
+
if (bz2err != BZ_OK && bz2err != BZ_STREAM_END) {
|
|
62
|
+
if (error) {
|
|
63
|
+
*error = MakePatchError(@"Failed to read bzip stream");
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (read <= 0) {
|
|
69
|
+
if (error) {
|
|
70
|
+
*error = MakePatchError(@"Unexpected end of ENDSLEY/BSDIFF43 stream");
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
offset += static_cast<size_t>(read);
|
|
76
|
+
if (bz2err == BZ_STREAM_END && offset < length) {
|
|
77
|
+
if (error) {
|
|
78
|
+
*error = MakePatchError(@"Unexpected end of ENDSLEY/BSDIFF43 stream");
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
bool WriteAll(FILE *file, const uint8_t *buffer, size_t length, NSError **error) {
|
|
88
|
+
size_t written = 0;
|
|
89
|
+
while (written < length) {
|
|
90
|
+
size_t result = fwrite(buffer + written, 1, length - written, file);
|
|
91
|
+
if (result == 0) {
|
|
92
|
+
if (error) {
|
|
93
|
+
*error = MakeSystemError(@"Failed to write patch output");
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
written += result;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
bool ReadBaseAt(int baseFd,
|
|
103
|
+
off_t baseSize,
|
|
104
|
+
int64_t offset,
|
|
105
|
+
uint8_t *target,
|
|
106
|
+
size_t count,
|
|
107
|
+
NSError **error) {
|
|
108
|
+
memset(target, 0, count);
|
|
109
|
+
if (count == 0 || offset >= baseSize ||
|
|
110
|
+
offset > INT64_MAX - static_cast<int64_t>(count) ||
|
|
111
|
+
offset + static_cast<int64_t>(count) <= 0) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
int64_t validStart = std::max<int64_t>(offset, 0);
|
|
116
|
+
int64_t validEnd = std::min<int64_t>(
|
|
117
|
+
offset + static_cast<int64_t>(count),
|
|
118
|
+
static_cast<int64_t>(baseSize));
|
|
119
|
+
if (validStart >= validEnd) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
size_t targetOffset = static_cast<size_t>(validStart - offset);
|
|
124
|
+
size_t validLength = static_cast<size_t>(validEnd - validStart);
|
|
125
|
+
size_t totalRead = 0;
|
|
126
|
+
while (totalRead < validLength) {
|
|
127
|
+
ssize_t read = pread(
|
|
128
|
+
baseFd,
|
|
129
|
+
target + targetOffset + totalRead,
|
|
130
|
+
validLength - totalRead,
|
|
131
|
+
static_cast<off_t>(validStart + static_cast<int64_t>(totalRead)));
|
|
132
|
+
if (read <= 0) {
|
|
133
|
+
if (error) {
|
|
134
|
+
*error = MakeSystemError(@"Failed to read base file");
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
totalRead += static_cast<size_t>(read);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
} // namespace
|
|
143
|
+
|
|
144
|
+
extern "C" BOOL HotUpdaterApplyBsdiffPatch(NSString *patchPath,
|
|
145
|
+
NSString *basePath,
|
|
146
|
+
NSString *outputPath) {
|
|
147
|
+
NSError *patchError = nil;
|
|
148
|
+
BOOL applied = [BsdiffPatchBridge applyPatchAtPath:patchPath
|
|
149
|
+
toBaseAtPath:basePath
|
|
150
|
+
outputAtPath:outputPath
|
|
151
|
+
error:&patchError];
|
|
152
|
+
if (!applied && patchError != nil) {
|
|
153
|
+
NSLog(@"[BundleStorage] Failed to apply bsdiff patch: %@",
|
|
154
|
+
patchError.localizedDescription);
|
|
155
|
+
}
|
|
156
|
+
return applied;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@implementation BsdiffPatchBridge
|
|
160
|
+
|
|
161
|
+
+ (BOOL)applyPatchAtPath:(NSString *)patchPath
|
|
162
|
+
toBaseAtPath:(NSString *)basePath
|
|
163
|
+
outputAtPath:(NSString *)outputPath
|
|
164
|
+
error:(NSError * _Nullable __autoreleasing *)error {
|
|
165
|
+
FILE *patchFile = nullptr;
|
|
166
|
+
FILE *outputFile = nullptr;
|
|
167
|
+
BZFILE *bz2 = nullptr;
|
|
168
|
+
int baseFd = -1;
|
|
169
|
+
BOOL success = NO;
|
|
170
|
+
NSString *tempOutputPath = [outputPath stringByAppendingString:@".tmp"];
|
|
171
|
+
|
|
172
|
+
auto cleanup = [&]() {
|
|
173
|
+
if (bz2 != nullptr) {
|
|
174
|
+
int bz2err = BZ_OK;
|
|
175
|
+
BZ2_bzReadClose(&bz2err, bz2);
|
|
176
|
+
bz2 = nullptr;
|
|
177
|
+
}
|
|
178
|
+
if (patchFile != nullptr) {
|
|
179
|
+
fclose(patchFile);
|
|
180
|
+
patchFile = nullptr;
|
|
181
|
+
}
|
|
182
|
+
if (baseFd >= 0) {
|
|
183
|
+
close(baseFd);
|
|
184
|
+
baseFd = -1;
|
|
185
|
+
}
|
|
186
|
+
if (outputFile != nullptr) {
|
|
187
|
+
fclose(outputFile);
|
|
188
|
+
outputFile = nullptr;
|
|
189
|
+
}
|
|
190
|
+
if (!success) {
|
|
191
|
+
unlink(tempOutputPath.fileSystemRepresentation);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
auto fail = [&](NSError *patchError) {
|
|
196
|
+
if (error) {
|
|
197
|
+
*error = patchError;
|
|
198
|
+
}
|
|
199
|
+
cleanup();
|
|
200
|
+
return NO;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
auto failMessage = [&](NSString *message) {
|
|
204
|
+
return fail(MakePatchError(message));
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
patchFile = fopen(patchPath.fileSystemRepresentation, "rb");
|
|
208
|
+
if (patchFile == nullptr) {
|
|
209
|
+
return fail(MakeSystemError(@"Failed to open patch file"));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
uint8_t header[24];
|
|
213
|
+
if (fread(header, 1, sizeof(header), patchFile) != sizeof(header)) {
|
|
214
|
+
return failMessage(@"Invalid ENDSLEY/BSDIFF43 header");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (memcmp(header, "ENDSLEY/BSDIFF43", 16) != 0) {
|
|
218
|
+
return failMessage(@"Invalid ENDSLEY/BSDIFF43 header");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
int64_t newSize = ReadOfft(header + 16);
|
|
222
|
+
|
|
223
|
+
if (newSize < 0) {
|
|
224
|
+
return failMessage(@"Negative ENDSLEY/BSDIFF43 target size");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (static_cast<uint64_t>(newSize) > NSUIntegerMax) {
|
|
228
|
+
return failMessage(@"Target file is too large");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
baseFd = open(basePath.fileSystemRepresentation, O_RDONLY);
|
|
232
|
+
if (baseFd < 0) {
|
|
233
|
+
return fail(MakeSystemError(@"Failed to open base file"));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
struct stat baseStat {};
|
|
237
|
+
if (fstat(baseFd, &baseStat) != 0) {
|
|
238
|
+
return fail(MakeSystemError(@"Failed to stat base file"));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
unlink(tempOutputPath.fileSystemRepresentation);
|
|
242
|
+
outputFile = fopen(tempOutputPath.fileSystemRepresentation, "wb");
|
|
243
|
+
if (outputFile == nullptr) {
|
|
244
|
+
return fail(MakeSystemError(@"Failed to open patch output file"));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
int bz2err = BZ_OK;
|
|
248
|
+
bz2 = BZ2_bzReadOpen(&bz2err, patchFile, 0, 0, nullptr, 0);
|
|
249
|
+
if (bz2 == nullptr || bz2err != BZ_OK) {
|
|
250
|
+
return failMessage(@"Failed to initialize bzip stream");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
std::vector<uint8_t> diffBuffer(kBzipChunkSize);
|
|
254
|
+
std::vector<uint8_t> baseBuffer(kBzipChunkSize);
|
|
255
|
+
std::vector<uint8_t> outputBuffer(kBzipChunkSize);
|
|
256
|
+
int64_t outputPos = 0;
|
|
257
|
+
int64_t oldPos = 0;
|
|
258
|
+
|
|
259
|
+
while (outputPos < newSize) {
|
|
260
|
+
uint8_t controlBytes[24];
|
|
261
|
+
if (!ReadBzipExactly(bz2, controlBytes, sizeof(controlBytes), error)) {
|
|
262
|
+
cleanup();
|
|
263
|
+
return NO;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
int64_t addLen = ReadOfft(controlBytes);
|
|
267
|
+
int64_t copyLen = ReadOfft(controlBytes + 8);
|
|
268
|
+
int64_t seekLen = ReadOfft(controlBytes + 16);
|
|
269
|
+
|
|
270
|
+
if (addLen < 0 || copyLen < 0) {
|
|
271
|
+
return failMessage(@"Negative add/copy length in control block");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
int64_t remainingOutput = newSize - outputPos;
|
|
275
|
+
|
|
276
|
+
if (addLen > remainingOutput || copyLen > remainingOutput - addLen) {
|
|
277
|
+
return failMessage(@"ENDSLEY/BSDIFF43 stream is truncated");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
size_t remainingAdd = static_cast<size_t>(addLen);
|
|
281
|
+
while (remainingAdd > 0) {
|
|
282
|
+
size_t chunkSize = std::min<size_t>(remainingAdd, kBzipChunkSize);
|
|
283
|
+
if (!ReadBzipExactly(bz2, diffBuffer.data(), chunkSize, error) ||
|
|
284
|
+
!ReadBaseAt(baseFd, baseStat.st_size, oldPos, baseBuffer.data(), chunkSize, error)) {
|
|
285
|
+
cleanup();
|
|
286
|
+
return NO;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (size_t index = 0; index < chunkSize; index += 1) {
|
|
290
|
+
outputBuffer[index] = static_cast<uint8_t>(
|
|
291
|
+
(diffBuffer[index] + baseBuffer[index]) & 0xFF);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!WriteAll(outputFile, outputBuffer.data(), chunkSize, error)) {
|
|
295
|
+
cleanup();
|
|
296
|
+
return NO;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
int64_t nextOldPos = 0;
|
|
300
|
+
if (!CheckedAddInt64(oldPos, static_cast<int64_t>(chunkSize), &nextOldPos)) {
|
|
301
|
+
return failMessage(@"Old file seek overflow");
|
|
302
|
+
}
|
|
303
|
+
oldPos = nextOldPos;
|
|
304
|
+
outputPos += static_cast<int64_t>(chunkSize);
|
|
305
|
+
remainingAdd -= chunkSize;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
size_t remainingCopy = static_cast<size_t>(copyLen);
|
|
309
|
+
while (remainingCopy > 0) {
|
|
310
|
+
size_t chunkSize = std::min<size_t>(remainingCopy, kBzipChunkSize);
|
|
311
|
+
if (!ReadBzipExactly(bz2, diffBuffer.data(), chunkSize, error) ||
|
|
312
|
+
!WriteAll(outputFile, diffBuffer.data(), chunkSize, error)) {
|
|
313
|
+
cleanup();
|
|
314
|
+
return NO;
|
|
315
|
+
}
|
|
316
|
+
outputPos += static_cast<int64_t>(chunkSize);
|
|
317
|
+
remainingCopy -= chunkSize;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
int64_t nextOldPos = 0;
|
|
321
|
+
if (!CheckedAddInt64(oldPos, seekLen, &nextOldPos)) {
|
|
322
|
+
return failMessage(@"Old file seek overflow");
|
|
323
|
+
}
|
|
324
|
+
oldPos = nextOldPos;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (fclose(outputFile) != 0) {
|
|
328
|
+
outputFile = nullptr;
|
|
329
|
+
return fail(MakeSystemError(@"Failed to close patch output file"));
|
|
330
|
+
}
|
|
331
|
+
outputFile = nullptr;
|
|
332
|
+
|
|
333
|
+
if (rename(tempOutputPath.fileSystemRepresentation,
|
|
334
|
+
outputPath.fileSystemRepresentation) != 0) {
|
|
335
|
+
return fail(MakeSystemError(@"Failed to move patch output file"));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
success = YES;
|
|
339
|
+
cleanup();
|
|
340
|
+
return YES;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@end
|