react-native-update 10.46.0 → 10.48.0

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.
Files changed (60) hide show
  1. package/android/jni/Android.mk +2 -0
  2. package/android/jni/hpatch.c +57 -2
  3. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  4. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  5. package/android/lib/x86/librnupdate.so +0 -0
  6. package/android/lib/x86_64/librnupdate.so +0 -0
  7. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +19 -4
  8. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +2 -0
  9. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +1 -0
  10. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateCore.java +12 -0
  11. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +30 -0
  12. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +13 -0
  13. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +1 -0
  14. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  15. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  16. package/cpp/patch_core/error_codes.h +3 -0
  17. package/cpp/patch_core/hbc_transform.cpp +143 -0
  18. package/cpp/patch_core/hbc_transform.h +53 -0
  19. package/cpp/patch_core/hbc_transform_wire.cpp +321 -0
  20. package/cpp/patch_core/hbc_transform_wire.h +52 -0
  21. package/cpp/patch_core/patch_core.cpp +126 -4
  22. package/cpp/patch_core/patch_core.h +5 -0
  23. package/cpp/patch_core/patch_core_android.cpp +3 -1
  24. package/cpp/patch_core/tests/fixtures/rawstored.lzma2label.patch.bin +0 -0
  25. package/cpp/patch_core/tests/fixtures/rawstored.new.bin +0 -0
  26. package/cpp/patch_core/tests/fixtures/rawstored.old.bin +0 -0
  27. package/cpp/patch_core/tests/fixtures/v96.hbc +0 -0
  28. package/cpp/patch_core/tests/fixtures/v96.meta.json +1 -0
  29. package/cpp/patch_core/tests/fixtures/v96.streampatch.bin +0 -0
  30. package/cpp/patch_core/tests/fixtures/v96.t.hbc +0 -0
  31. package/cpp/patch_core/tests/fixtures/v96.tpatch.bin +0 -0
  32. package/cpp/patch_core/tests/fixtures/v96.tstreampatch.bin +0 -0
  33. package/cpp/patch_core/tests/fixtures/v96b.hbc +0 -0
  34. package/cpp/patch_core/tests/fixtures/v98.hbc +0 -0
  35. package/cpp/patch_core/tests/fixtures/v98.t.hbc +0 -0
  36. package/cpp/patch_core/tests/fixtures/v98b.hbc +0 -0
  37. package/cpp/patch_core/tests/fixtures/v98b.t.hbc +0 -0
  38. package/cpp/patch_core/tests/hbc_transform_test.cpp +229 -0
  39. package/cpp/patch_core/tests/patch_core_test.cpp +150 -1
  40. package/cpp/patch_core/update_core_android.cpp +10 -0
  41. package/harmony/pushy/src/main/cpp/CMakeLists.txt +2 -0
  42. package/harmony/pushy/src/main/cpp/pushy.cpp +18 -1
  43. package/harmony/pushy/src/main/ets/DownloadTask.ts +21 -0
  44. package/harmony/pushy/src/main/ets/NativePatchCore.ts +4 -0
  45. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +12 -0
  46. package/harmony/pushy/src/main/ets/UpdateContext.ts +46 -0
  47. package/harmony/pushy.har +0 -0
  48. package/ios/RCTPushy/RCTPushy.mm +64 -0
  49. package/package.json +1 -1
  50. package/react-native-update.podspec +2 -0
  51. package/src/NativePushy.ts +1 -0
  52. package/src/client.ts +123 -0
  53. package/src/context.ts +4 -0
  54. package/src/core.ts +4 -0
  55. package/src/error.ts +1 -0
  56. package/src/locales/en.ts +2 -0
  57. package/src/locales/zh.ts +2 -0
  58. package/src/provider.tsx +9 -0
  59. package/src/telemetry.ts +83 -0
  60. package/src/type.ts +8 -0
@@ -0,0 +1,229 @@
1
+ // hbc_transform 宿主机测试:
2
+ // 1) 与 JS 参考实现(react-native-update-cli src/utils/hbcTransform.ts)
3
+ // 做 golden 对拍——fixtures/*.t.hbc 由 JS 实现生成;
4
+ // 2) 可逆性 property check;
5
+ // 3) 非法输入/非法描述表拒绝(buffer 必须保持原样)。
6
+ #include "../hbc_transform.h"
7
+
8
+ #include <cstdio>
9
+ #include <cstring>
10
+ #include <fstream>
11
+ #include <string>
12
+ #include <vector>
13
+
14
+ namespace {
15
+
16
+ using pushy::hbc::HbcDeltaField;
17
+ using pushy::hbc::HbcLayoutDesc;
18
+ using pushy::hbc::HbcSectionDesc;
19
+ using pushy::hbc::TransformHbcInPlace;
20
+
21
+ int g_failures = 0;
22
+
23
+ #define CHECK(cond) \
24
+ do { \
25
+ if (!(cond)) { \
26
+ std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
27
+ ++g_failures; \
28
+ } \
29
+ } while (0)
30
+
31
+ std::vector<uint8_t> ReadFileOrDie(const std::string& path) {
32
+ std::ifstream in(path, std::ios::binary);
33
+ if (!in) {
34
+ std::fprintf(stderr, "cannot open fixture: %s\n", path.c_str());
35
+ std::exit(1);
36
+ }
37
+ return std::vector<uint8_t>(
38
+ (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
39
+ }
40
+
41
+ // ---- 布局描述表(测试用常量;生产路径从 patch 元数据的 wire 格式构建)----
42
+ // 与 CLI 的 HBC_LAYOUTS 逐字段对应。counts 槽位约定:0 = fileLength,
43
+ // 最后一个 = debugInfoOffset。
44
+
45
+ const HbcDeltaField kFuncOffset25[] = {{0, 0, 25}};
46
+ const HbcDeltaField kFuncOffsets25x2[] = {{0, 0, 25}, {8, 0, 25}};
47
+ const HbcDeltaField kStringOffset23[] = {{0, 1, 23}};
48
+ const HbcDeltaField kPairOffset32[] = {{0, 0, 32}};
49
+
50
+ // v87~v96:19 个计数槽
51
+ // [0]fileLength [1]globalCodeIndex [2]functionCount [3]stringKindCount
52
+ // [4]identifierCount [5]stringCount [6]overflowStringCount
53
+ // [7]stringStorageSize [8]bigIntCount [9]bigIntStorageSize [10]regExpCount
54
+ // [11]regExpStorageSize [12]arrayBufferSize [13]objKeyBufferSize
55
+ // [14]objValueBufferSize [15]segmentID [16]cjsModuleCount
56
+ // [17]functionSourceCount [18]debugInfoOffset
57
+ const HbcSectionDesc kSectionsV96[] = {
58
+ {2, 16, kFuncOffsets25x2, 2}, // functionHeaders
59
+ {3, 4, nullptr, 0}, // stringKinds
60
+ {4, 4, nullptr, 0}, // identifierHashes
61
+ {5, 4, kStringOffset23, 1}, // smallStringTable
62
+ {6, 8, kPairOffset32, 1}, // overflowStringTable
63
+ {7, 1, nullptr, 0}, // stringStorage
64
+ {12, 1, nullptr, 0}, // arrayBuffer
65
+ {13, 1, nullptr, 0}, // objKeyBuffer
66
+ {14, 1, nullptr, 0}, // objValueBuffer
67
+ {8, 8, kPairOffset32, 1}, // bigIntTable
68
+ {9, 1, nullptr, 0}, // bigIntStorage
69
+ {10, 8, kPairOffset32, 1}, // regExpTable
70
+ {11, 1, nullptr, 0}, // regExpStorage
71
+ {16, 8, nullptr, 0}, // cjsModuleTable
72
+ {17, 8, nullptr, 0}, // functionSourceTable
73
+ };
74
+ const HbcLayoutDesc kLayoutV96 = {19, kSectionsV96, 15};
75
+
76
+ // v98 早期变体(Static Hermes,19 槽):SmallFuncHeader 12B、
77
+ // objShapeTable 取代 objValueBuffer
78
+ const HbcSectionDesc kSectionsV98[] = {
79
+ {2, 12, kFuncOffset25, 1}, // functionHeaders
80
+ {3, 4, nullptr, 0}, // stringKinds
81
+ {4, 4, nullptr, 0}, // identifierHashes
82
+ {5, 4, kStringOffset23, 1}, // smallStringTable
83
+ {6, 8, kPairOffset32, 1}, // overflowStringTable
84
+ {7, 1, nullptr, 0}, // stringStorage
85
+ {12, 1, nullptr, 0}, // literalValueBuffer
86
+ {13, 1, nullptr, 0}, // objKeyBuffer
87
+ {14, 8, kPairOffset32, 1}, // objShapeTable
88
+ {8, 8, kPairOffset32, 1}, // bigIntTable
89
+ {9, 1, nullptr, 0}, // bigIntStorage
90
+ {10, 8, kPairOffset32, 1}, // regExpTable
91
+ {11, 1, nullptr, 0}, // regExpStorage
92
+ {16, 8, nullptr, 0}, // cjsModuleTable
93
+ {17, 8, nullptr, 0}, // functionSourceTable
94
+ };
95
+ const HbcLayoutDesc kLayoutV98 = {19, kSectionsV98, 15};
96
+
97
+ // v98 晚期变体(生产主流,20 槽):槽 15 = numStringSwitchImms,
98
+ // 其后 segmentID/cjs/functionSource/debugInfo 依次后移一位
99
+ const HbcSectionDesc kSectionsV98Late[] = {
100
+ {2, 12, kFuncOffset25, 1}, // functionHeaders
101
+ {3, 4, nullptr, 0}, // stringKinds
102
+ {4, 4, nullptr, 0}, // identifierHashes
103
+ {5, 4, kStringOffset23, 1}, // smallStringTable
104
+ {6, 8, kPairOffset32, 1}, // overflowStringTable
105
+ {7, 1, nullptr, 0}, // stringStorage
106
+ {12, 1, nullptr, 0}, // literalValueBuffer
107
+ {13, 1, nullptr, 0}, // objKeyBuffer
108
+ {14, 8, kPairOffset32, 1}, // objShapeTable
109
+ {8, 8, kPairOffset32, 1}, // bigIntTable
110
+ {9, 1, nullptr, 0}, // bigIntStorage
111
+ {10, 8, kPairOffset32, 1}, // regExpTable
112
+ {11, 1, nullptr, 0}, // regExpStorage
113
+ {17, 8, nullptr, 0}, // cjsModuleTable
114
+ {18, 8, nullptr, 0}, // functionSourceTable
115
+ };
116
+ const HbcLayoutDesc kLayoutV98Late = {20, kSectionsV98Late, 15};
117
+
118
+ void TestGoldenPair(
119
+ const std::string& dir,
120
+ const char* plainName,
121
+ const char* goldenName,
122
+ const HbcLayoutDesc& layout) {
123
+ const std::vector<uint8_t> plain = ReadFileOrDie(dir + plainName);
124
+ const std::vector<uint8_t> golden = ReadFileOrDie(dir + goldenName);
125
+ CHECK(plain.size() == golden.size());
126
+
127
+ // T(plain) == golden(与 JS 实现逐字节一致)
128
+ std::vector<uint8_t> forward = plain;
129
+ CHECK(TransformHbcInPlace(forward.data(), forward.size(), layout, false));
130
+ CHECK(forward == golden);
131
+ CHECK(forward != plain); // 确实改写了字节
132
+
133
+ // T⁻¹(golden) == plain
134
+ std::vector<uint8_t> backward = golden;
135
+ CHECK(TransformHbcInPlace(backward.data(), backward.size(), layout, true));
136
+ CHECK(backward == plain);
137
+ }
138
+
139
+ void TestRejections(const std::string& dir) {
140
+ const std::vector<uint8_t> good = ReadFileOrDie(dir + "v98.hbc");
141
+
142
+ // 太短
143
+ {
144
+ std::vector<uint8_t> buf(good.begin(), good.begin() + 64);
145
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), kLayoutV98, false));
146
+ }
147
+ // 坏 magic → 拒绝且 buffer 不被修改
148
+ {
149
+ std::vector<uint8_t> buf = good;
150
+ buf[0] ^= 0xff;
151
+ const std::vector<uint8_t> before = buf;
152
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), kLayoutV98, false));
153
+ CHECK(buf == before);
154
+ }
155
+ // fileLength 与实际大小不符(截断)
156
+ {
157
+ std::vector<uint8_t> buf(good.begin(), good.end() - 8);
158
+ const std::vector<uint8_t> before = buf;
159
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), kLayoutV98, false));
160
+ CHECK(buf == before);
161
+ }
162
+ // 计数爆表 → 段越界
163
+ {
164
+ std::vector<uint8_t> buf = good;
165
+ buf[32 + 2 * 4] = 0xff;
166
+ buf[32 + 2 * 4 + 1] = 0xff;
167
+ buf[32 + 2 * 4 + 2] = 0xff;
168
+ buf[32 + 2 * 4 + 3] = 0x0f;
169
+ const std::vector<uint8_t> before = buf;
170
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), kLayoutV98, false));
171
+ CHECK(buf == before);
172
+ }
173
+ // 非法描述表:countIndex 越界
174
+ {
175
+ std::vector<uint8_t> buf = good;
176
+ const HbcSectionDesc bad[] = {{99, 4, nullptr, 0}};
177
+ const HbcLayoutDesc badLayout = {19, bad, 1};
178
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), badLayout, false));
179
+ }
180
+ // 非法描述表:差分字段超出条目
181
+ {
182
+ std::vector<uint8_t> buf = good;
183
+ const HbcDeltaField badField[] = {{4, 0, 32}}; // byte 4 + 4 > entrySize 4
184
+ const HbcSectionDesc bad[] = {{5, 4, badField, 1}};
185
+ const HbcLayoutDesc badLayout = {19, bad, 1};
186
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), badLayout, false));
187
+ }
188
+ // 非法描述表:位域越过 32 位
189
+ {
190
+ std::vector<uint8_t> buf = good;
191
+ const HbcDeltaField badField[] = {{0, 8, 25}};
192
+ const HbcSectionDesc bad[] = {{5, 4, badField, 1}};
193
+ const HbcLayoutDesc badLayout = {19, bad, 1};
194
+ CHECK(!TransformHbcInPlace(buf.data(), buf.size(), badLayout, false));
195
+ }
196
+ }
197
+
198
+ } // namespace
199
+
200
+ int main(int argc, char** argv) {
201
+ std::string fixturesDir = "cpp/patch_core/tests/fixtures/";
202
+ if (argc > 1) {
203
+ fixturesDir = argv[1];
204
+ if (!fixturesDir.empty() && fixturesDir.back() != '/') {
205
+ fixturesDir += '/';
206
+ }
207
+ }
208
+
209
+ TestGoldenPair(fixturesDir, "v96.hbc", "v96.t.hbc", kLayoutV96);
210
+ TestGoldenPair(fixturesDir, "v98.hbc", "v98.t.hbc", kLayoutV98);
211
+ TestGoldenPair(fixturesDir, "v98b.hbc", "v98b.t.hbc", kLayoutV98Late);
212
+ TestRejections(fixturesDir);
213
+
214
+ // 变体互斥:19 槽布局作用于 20 槽文件(或反之)必须被结构校验拒绝
215
+ {
216
+ std::vector<uint8_t> late = ReadFileOrDie(fixturesDir + "v98b.hbc");
217
+ CHECK(!TransformHbcInPlace(late.data(), late.size(), kLayoutV98, false));
218
+ std::vector<uint8_t> early = ReadFileOrDie(fixturesDir + "v98.hbc");
219
+ CHECK(
220
+ !TransformHbcInPlace(early.data(), early.size(), kLayoutV98Late, false));
221
+ }
222
+
223
+ if (g_failures > 0) {
224
+ std::fprintf(stderr, "hbc_transform_test: %d failure(s)\n", g_failures);
225
+ return 1;
226
+ }
227
+ std::printf("hbc_transform_test: all checks passed\n");
228
+ return 0;
229
+ }
@@ -137,6 +137,146 @@ void ExpectEq(const std::string& left, const std::string& right, const std::stri
137
137
  }
138
138
  }
139
139
 
140
+ std::string g_fixtures_dir = "cpp/patch_core/tests/fixtures";
141
+
142
+ // 变换域 bundle patch 端到端:真实 hpatch + wire 元数据 → T(origin) →
143
+ // hpatch → T⁻¹,结果必须与新 bundle 逐字节一致(fixtures 由 CLI 的 JS
144
+ // 实现生成,同时充当跨实现 golden)。
145
+ void TestApplyPatchWithHbcTransform() {
146
+ TempDir temp;
147
+ const std::string origin = JoinPath(g_fixtures_dir, "v96.hbc");
148
+ const std::string patch = JoinPath(g_fixtures_dir, "v96.tpatch.bin");
149
+ const std::string expected = ReadFile(JoinPath(g_fixtures_dir, "v96b.hbc"));
150
+ const std::string meta = ReadFile(JoinPath(g_fixtures_dir, "v96.meta.json"));
151
+ Expect(!expected.empty() && !meta.empty(), "hbc fixtures must exist");
152
+
153
+ FileSourcePatchOptions options;
154
+ options.source_root = JoinPath(temp.path, "src");
155
+ options.target_root = JoinPath(temp.path, "dst");
156
+ options.origin_bundle_path = origin;
157
+ options.bundle_patch_path = patch;
158
+ options.bundle_output_path = JoinPath(temp.path, "out/index.bundlejs");
159
+ options.enable_merge = false;
160
+ options.bundle_hbc_transform_meta = meta;
161
+
162
+ Status status = ApplyPatchFromFileSource(options);
163
+ Expect(status.ok, "hbc transform patch should succeed: " + status.message);
164
+ ExpectEq(
165
+ ReadFile(options.bundle_output_path).size() == expected.size()
166
+ ? std::string("same-size")
167
+ : std::string("size-mismatch"),
168
+ "same-size",
169
+ "restored bundle size");
170
+ Expect(
171
+ ReadFile(options.bundle_output_path) == expected,
172
+ "restored bundle must equal new bundle byte-for-byte");
173
+
174
+ // 临时文件必须被清理
175
+ Expect(
176
+ !Exists(options.bundle_output_path + ".hbct-origin") &&
177
+ !Exists(options.bundle_output_path + ".hbct-patched"),
178
+ "hbc transform temp files must be removed");
179
+ }
180
+
181
+ void TestApplyPatchWithHbcTransformRejectsBadMeta() {
182
+ TempDir temp;
183
+ FileSourcePatchOptions options;
184
+ options.source_root = JoinPath(temp.path, "src");
185
+ options.target_root = JoinPath(temp.path, "dst");
186
+ options.origin_bundle_path = JoinPath(g_fixtures_dir, "v96.hbc");
187
+ options.bundle_patch_path = JoinPath(g_fixtures_dir, "v96.tpatch.bin");
188
+ options.bundle_output_path = JoinPath(temp.path, "out/index.bundlejs");
189
+ options.enable_merge = false;
190
+
191
+ // 不可解析的元数据 → 失败(绝不能忽略元数据直接 hpatch)
192
+ options.bundle_hbc_transform_meta = "not json";
193
+ Expect(
194
+ !ApplyPatchFromFileSource(options).ok,
195
+ "malformed hbcTransform metadata must fail");
196
+ Expect(!Exists(options.bundle_output_path), "no output on failure");
197
+
198
+ // 不支持的变换规范版本 → 失败(调用方回退整包)
199
+ std::string meta = ReadFile(JoinPath(g_fixtures_dir, "v96.meta.json"));
200
+ const std::string needle = "\"v\":1";
201
+ const size_t pos = meta.find(needle);
202
+ Expect(pos != std::string::npos, "meta fixture must contain v:1");
203
+ meta.replace(pos, needle.size(), "\"v\":9");
204
+ options.bundle_hbc_transform_meta = meta;
205
+ Status status = ApplyPatchFromFileSource(options);
206
+ Expect(!status.ok, "unsupported hbcTransform version must fail");
207
+ Expect(
208
+ status.message.find("Unsupported hbcTransform version") !=
209
+ std::string::npos,
210
+ "error should identify unsupported version: " + status.message);
211
+ }
212
+
213
+ // HDIFF13(diffStream)格式:hpatch_by_file 按 magic 自动分派,
214
+ // v2 轨道的大 bundle patch 走此路径。fixtures 由 node-hdiffpatch 生成并
215
+ // 已在 JS 侧验证过 round-trip。
216
+ void TestApplyStreamFormatBundlePatch() {
217
+ TempDir temp;
218
+ FileSourcePatchOptions options;
219
+ options.source_root = JoinPath(temp.path, "src");
220
+ options.target_root = JoinPath(temp.path, "dst");
221
+ options.origin_bundle_path = JoinPath(g_fixtures_dir, "v96.hbc");
222
+ options.bundle_patch_path = JoinPath(g_fixtures_dir, "v96.streampatch.bin");
223
+ options.bundle_output_path = JoinPath(temp.path, "out/index.bundlejs");
224
+ options.enable_merge = false;
225
+
226
+ Status status = ApplyPatchFromFileSource(options);
227
+ Expect(status.ok, "stream-format patch should apply: " + status.message);
228
+ Expect(
229
+ ReadFile(options.bundle_output_path) ==
230
+ ReadFile(JoinPath(g_fixtures_dir, "v96b.hbc")),
231
+ "stream-format restored bundle must equal new bundle");
232
+ }
233
+
234
+ void TestApplyStreamFormatWithHbcTransform() {
235
+ TempDir temp;
236
+ FileSourcePatchOptions options;
237
+ options.source_root = JoinPath(temp.path, "src");
238
+ options.target_root = JoinPath(temp.path, "dst");
239
+ options.origin_bundle_path = JoinPath(g_fixtures_dir, "v96.hbc");
240
+ options.bundle_patch_path = JoinPath(g_fixtures_dir, "v96.tstreampatch.bin");
241
+ options.bundle_output_path = JoinPath(temp.path, "out/index.bundlejs");
242
+ options.enable_merge = false;
243
+ options.bundle_hbc_transform_meta =
244
+ ReadFile(JoinPath(g_fixtures_dir, "v96.meta.json"));
245
+
246
+ Status status = ApplyPatchFromFileSource(options);
247
+ Expect(
248
+ status.ok,
249
+ "transform + stream-format patch should apply: " + status.message);
250
+ Expect(
251
+ ReadFile(options.bundle_output_path) ==
252
+ ReadFile(JoinPath(g_fixtures_dir, "v96b.hbc")),
253
+ "transform+stream restored bundle must equal new bundle");
254
+ }
255
+
256
+ // HDIFFSF20 允许 compressedSize==0 表示 diff payload 为 RAW。node-hdiffpatch
257
+ // 2.x 的 v5 writer 在这种小 diff 上仍会保留 lzma2 标签,客户端必须按
258
+ // compressedSize 判定是否解压,不能只看 compressType。
259
+ void TestApplySingleFormatRawStoredWithLzma2Label() {
260
+ TempDir temp;
261
+ FileSourcePatchOptions options;
262
+ options.source_root = JoinPath(temp.path, "src");
263
+ options.target_root = JoinPath(temp.path, "dst");
264
+ options.origin_bundle_path = JoinPath(g_fixtures_dir, "rawstored.old.bin");
265
+ options.bundle_patch_path =
266
+ JoinPath(g_fixtures_dir, "rawstored.lzma2label.patch.bin");
267
+ options.bundle_output_path = JoinPath(temp.path, "out/index.bundlejs");
268
+ options.enable_merge = false;
269
+
270
+ Status status = ApplyPatchFromFileSource(options);
271
+ Expect(
272
+ status.ok,
273
+ "raw-stored single-format patch should apply: " + status.message);
274
+ Expect(
275
+ ReadFile(options.bundle_output_path) ==
276
+ ReadFile(JoinPath(g_fixtures_dir, "rawstored.new.bin")),
277
+ "raw-stored single-format restored bundle must equal new bundle");
278
+ }
279
+
140
280
  void TestApplyPatchFromFileSourceMergesAndCopies() {
141
281
  TempDir temp;
142
282
  const std::string source = JoinPath(temp.path, "origin");
@@ -609,8 +749,17 @@ void TestStateCoreSwitchToSameVersion() {
609
749
 
610
750
  } // namespace
611
751
 
612
- int main() {
752
+ int main(int argc, char** argv) {
753
+ if (argc > 1) {
754
+ g_fixtures_dir = argv[1];
755
+ }
613
756
  const std::vector<std::pair<std::string, void (*)()>> tests = {
757
+ {"ApplyStreamFormatBundlePatch", TestApplyStreamFormatBundlePatch},
758
+ {"ApplyStreamFormatWithHbcTransform", TestApplyStreamFormatWithHbcTransform},
759
+ {"ApplySingleFormatRawStoredWithLzma2Label",
760
+ TestApplySingleFormatRawStoredWithLzma2Label},
761
+ {"ApplyPatchWithHbcTransform", TestApplyPatchWithHbcTransform},
762
+ {"ApplyPatchWithHbcTransformRejectsBadMeta", TestApplyPatchWithHbcTransformRejectsBadMeta},
614
763
  {"ApplyPatchFromFileSourceMergesAndCopies", TestApplyPatchFromFileSourceMergesAndCopies},
615
764
  {"ApplyPatchMergeHardLinksUnchangedFiles", TestApplyPatchMergeHardLinksUnchangedFiles},
616
765
  {"ApplyPatchMergeFallsBackToByteCopy", TestApplyPatchMergeFallsBackToByteCopy},
@@ -4,6 +4,7 @@
4
4
  #include <vector>
5
5
 
6
6
  #include "archive_patch_core.h"
7
+ #include "hbc_transform_wire.h"
7
8
  #include "jni_util.h"
8
9
  #include "state_core.h"
9
10
  #include "state_ops.h"
@@ -289,6 +290,15 @@ jobject MakeStateResult(
289
290
 
290
291
  } // namespace
291
292
 
293
+ // 客户端可消费的 diff 轨道版本(能力上报,而非 SDK 版本映射);JS 层经
294
+ // getConstants 暴露,随 checkUpdate 以 diffV 上报,服务端据此下发 v2 轨道。
295
+ extern "C" JNIEXPORT jint JNICALL
296
+ Java_cn_reactnative_modules_update_NativeUpdateCore_getSupportedDiffVersion(
297
+ JNIEnv*,
298
+ jclass) {
299
+ return static_cast<jint>(pushy::hbc::kSupportedDiffVersion);
300
+ }
301
+
292
302
  extern "C" JNIEXPORT jobject JNICALL
293
303
  Java_cn_reactnative_modules_update_UpdateContext_syncStateWithBinaryVersion(
294
304
  JNIEnv* env,
@@ -34,6 +34,8 @@ else()
34
34
  set(HDP_SOURCES
35
35
  ${CMAKE_CURRENT_SOURCE_DIR}/pushy.cpp
36
36
  ${PATCH_CORE_DIR}/archive_patch_core.cpp
37
+ ${PATCH_CORE_DIR}/hbc_transform.cpp
38
+ ${PATCH_CORE_DIR}/hbc_transform_wire.cpp
37
39
  ${PATCH_CORE_DIR}/patch_core.cpp
38
40
  ${PATCH_CORE_DIR}/state_core.cpp
39
41
  ${ANDROID_JNI_DIR}/hpatch.c
@@ -6,6 +6,7 @@
6
6
  #include <vector>
7
7
 
8
8
  #include "archive_patch_core.h"
9
+ #include "hbc_transform_wire.h"
9
10
  #include "patch_core.h"
10
11
  #include "state_core.h"
11
12
  #include "state_ops.h"
@@ -686,6 +687,7 @@ napi_value ApplyPatchFromFileSource(napi_env env, napi_callback_info info) {
686
687
  std::string bundle_patch_path;
687
688
  std::string bundle_output_path;
688
689
  std::string merge_source_subdir;
690
+ std::string bundle_hbc_transform_meta;
689
691
  bool enable_merge = true;
690
692
 
691
693
  if (!GetOptionalStringArrayProperty(env, args[0], "copyFroms", &copy_froms) ||
@@ -697,6 +699,8 @@ napi_value ApplyPatchFromFileSource(napi_env env, napi_callback_info info) {
697
699
  !GetOptionalStringProperty(env, args[0], "bundlePatchPath", &bundle_patch_path) ||
698
700
  !GetOptionalStringProperty(env, args[0], "bundleOutputPath", &bundle_output_path) ||
699
701
  !GetOptionalStringProperty(env, args[0], "mergeSourceSubdir", &merge_source_subdir) ||
702
+ !GetOptionalStringProperty(
703
+ env, args[0], "bundleHbcTransformMeta", &bundle_hbc_transform_meta) ||
700
704
  !GetOptionalBoolProperty(env, args[0], "enableMerge", true, &enable_merge)) {
701
705
  return nullptr;
702
706
  }
@@ -713,6 +717,7 @@ napi_value ApplyPatchFromFileSource(napi_env env, napi_callback_info info) {
713
717
  work_data->options.bundle_patch_path = bundle_patch_path;
714
718
  work_data->options.bundle_output_path = bundle_output_path;
715
719
  work_data->options.merge_source_subdir = merge_source_subdir;
720
+ work_data->options.bundle_hbc_transform_meta = bundle_hbc_transform_meta;
716
721
  work_data->options.enable_merge = enable_merge;
717
722
 
718
723
  napi_value promise = nullptr;
@@ -876,13 +881,25 @@ bool ExportFunction(
876
881
 
877
882
  } // namespace
878
883
 
884
+
885
+ // 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道)
886
+ static napi_value GetSupportedDiffVersion(napi_env env, napi_callback_info) {
887
+ napi_value result = nullptr;
888
+ napi_create_uint32(
889
+ env,
890
+ static_cast<uint32_t>(pushy::hbc::kSupportedDiffVersion),
891
+ &result);
892
+ return result;
893
+ }
894
+
879
895
  napi_value Init(napi_env env, napi_value exports) {
880
896
  if (!ExportFunction(env, exports, "syncStateWithBinaryVersion", SyncStateWithBinaryVersion) ||
881
897
  !ExportFunction(env, exports, "runStateCore", RunStateCore) ||
882
898
  !ExportFunction(env, exports, "buildArchivePatchPlan", BuildArchivePatchPlan) ||
883
899
  !ExportFunction(env, exports, "buildCopyGroups", BuildCopyGroups) ||
884
900
  !ExportFunction(env, exports, "applyPatchFromFileSource", ApplyPatchFromFileSource) ||
885
- !ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries)) {
901
+ !ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries) ||
902
+ !ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion)) {
886
903
  return nullptr;
887
904
  }
888
905
  return exports;
@@ -16,6 +16,9 @@ export interface PatchManifestArrays {
16
16
  copyFroms: string[];
17
17
  copyTos: string[];
18
18
  deletes: string[];
19
+ // __diff.json 中对应 bundle patch 条目的 hbcTransform 元数据(原始 JSON
20
+ // 字符串);为空时 native 走现状路径
21
+ hbcTransformMeta: string;
19
22
  }
20
23
 
21
24
  export function parseManifestToArrays(
@@ -44,10 +47,23 @@ export function parseManifestToArrays(
44
47
  copyTos.push(to);
45
48
  }
46
49
 
50
+ const hbcTransform = manifest.hbcTransform as
51
+ | Record<string, object>
52
+ | undefined;
53
+ const hbcTransformEntry =
54
+ hbcTransform && typeof hbcTransform === 'object'
55
+ ? hbcTransform[HARMONY_BUNDLE_PATCH_ENTRY]
56
+ : undefined;
57
+ const hbcTransformMeta =
58
+ hbcTransformEntry && typeof hbcTransformEntry === 'object'
59
+ ? JSON.stringify(hbcTransformEntry)
60
+ : '';
61
+
47
62
  return {
48
63
  copyFroms,
49
64
  copyTos,
50
65
  deletes,
66
+ hbcTransformMeta,
51
67
  };
52
68
  }
53
69
 
@@ -214,6 +230,7 @@ export class DownloadTask {
214
230
  copyFroms: [],
215
231
  copyTos: [],
216
232
  deletes: [],
233
+ hbcTransformMeta: '',
217
234
  };
218
235
  }
219
236
 
@@ -229,6 +246,7 @@ export class DownloadTask {
229
246
  workingDirectory: string,
230
247
  bundlePatchPath: string,
231
248
  outputFile: string,
249
+ hbcTransformMeta = '',
232
250
  ): Promise<void> {
233
251
  const originBundlePath = `${workingDirectory}/${TEMP_ORIGIN_BUNDLE_ENTRY}`;
234
252
  try {
@@ -243,6 +261,7 @@ export class DownloadTask {
243
261
  bundlePatchPath,
244
262
  bundleOutputPath: outputFile,
245
263
  enableMerge: false,
264
+ bundleHbcTransformMeta: hbcTransformMeta,
246
265
  });
247
266
  } catch (error: any) {
248
267
  error.message = `Failed to process bundle patch: ${error.message}`;
@@ -534,6 +553,7 @@ export class DownloadTask {
534
553
  params.unzipDirectory,
535
554
  bundlePatchPath,
536
555
  `${params.unzipDirectory}/bundle.harmony.js`,
556
+ manifestArrays.hbcTransformMeta,
537
557
  );
538
558
  await this.copyFromResource(
539
559
  NativePatchCore.buildCopyGroups(
@@ -580,6 +600,7 @@ export class DownloadTask {
580
600
  bundleOutputPath: `${params.unzipDirectory}/bundle.harmony.js`,
581
601
  mergeSourceSubdir: plan.mergeSourceSubdir,
582
602
  enableMerge: plan.enableMerge,
603
+ bundleHbcTransformMeta: manifestArrays.hbcTransformMeta,
583
604
  });
584
605
  console.info('Patch from PPK completed');
585
606
  try {
@@ -47,6 +47,8 @@ export interface FileSourcePatchRequest {
47
47
  bundleOutputPath: string;
48
48
  mergeSourceSubdir?: string;
49
49
  enableMerge?: boolean;
50
+ /** __diff.json 中该 bundle patch 条目的 hbcTransform 元数据(原始 JSON) */
51
+ bundleHbcTransformMeta?: string;
50
52
  }
51
53
 
52
54
  interface NativePatchCoreBindings {
@@ -78,6 +80,8 @@ interface NativePatchCoreBindings {
78
80
  keepPrevious: string,
79
81
  maxAgeDays: number,
80
82
  ): Promise<void>;
83
+ /** 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道) */
84
+ getSupportedDiffVersion(): number;
81
85
  }
82
86
 
83
87
  export default NativeUpdateCore as unknown as NativePatchCoreBindings;
@@ -5,6 +5,7 @@ import {
5
5
  import common from '@ohos.app.ability.common';
6
6
  import { bundleManager } from '@kit.AbilityKit';
7
7
  import logger from './Logger';
8
+ import NativePatchCore from './NativePatchCore';
8
9
  import { UpdateContext } from './UpdateContext';
9
10
  import { EventHub } from './EventHub';
10
11
 
@@ -150,6 +151,7 @@ export class PushyTurboModule extends UITurboModule {
150
151
  isFirstTime,
151
152
  rolledBackVersion,
152
153
  uuid,
154
+ supportedDiffVersion: NativePatchCore.getSupportedDiffVersion(),
153
155
  };
154
156
  const logResult = {
155
157
  downloadRootDir: result.downloadRootDir,
@@ -231,6 +233,16 @@ export class PushyTurboModule extends UITurboModule {
231
233
  }
232
234
  }
233
235
 
236
+ async resetToPackagedBundle(): Promise<void> {
237
+ logger.debug(TAG, ',call resetToPackagedBundle');
238
+ try {
239
+ this.context.resetToPackagedBundle();
240
+ } catch (error) {
241
+ logger.error(TAG, `resetToPackagedBundle failed: ${getErrorMessage(error)}`);
242
+ throw Error(`resetToPackagedBundle failed: ${getErrorMessage(error)}`);
243
+ }
244
+ }
245
+
234
246
  async downloadPatchFromPpk(options: {
235
247
  updateUrl: string;
236
248
  hash: string;
@@ -372,6 +372,52 @@ export class UpdateContext {
372
372
  });
373
373
  }
374
374
 
375
+ /**
376
+ * 恢复到二进制内置包:清空整个更新状态机(下次启动即回内置 bundle)并删除
377
+ * 全部已下载版本。仅保留 uuid —— 它标识安装实例、用于灰度分桶,reset 不应改变。
378
+ */
379
+ public resetToPackagedBundle(): void {
380
+ this.trace('resetToPackagedBundle:before');
381
+ const state = this.getStateSnapshot();
382
+ const resetState: StateCoreResult = {
383
+ packageVersion: state.packageVersion,
384
+ buildTime: state.buildTime,
385
+ currentVersion: '',
386
+ lastVersion: '',
387
+ firstTime: false,
388
+ firstTimeOk: true,
389
+ rolledBackVersion: '',
390
+ };
391
+ // 删除已下载版本的 hash_* 元信息(不走 clear():它是异步的,且会连带清掉
392
+ // uuid —— 见 syncStateWithBinaryVersion 的注释)。getAllSync 在旧 SDK 上
393
+ // 可能不存在,此时残留的 hash_* 只是无害孤儿数据,不影响 reset 语义。
394
+ const prefsWithGetAll = this.preferences as preferences.Preferences & {
395
+ getAllSync?: () => Record<string, unknown>;
396
+ };
397
+ if (typeof prefsWithGetAll.getAllSync === 'function') {
398
+ try {
399
+ const all = prefsWithGetAll.getAllSync();
400
+ for (const key of Object.keys(all)) {
401
+ if (key.startsWith('hash_')) {
402
+ this.preferences.deleteSync(key);
403
+ }
404
+ }
405
+ } catch (e: any) {
406
+ console.error('Failed to clear hash info on reset:', e);
407
+ }
408
+ }
409
+ this.persistState(resetState, { clearFirstLoadMarker: true });
410
+ UpdateContext.ignoreRollback = false;
411
+
412
+ // maxAgeDays=0 且不保留任何版本:全量删除下载目录内容(后台线程,尽力而为)
413
+ NativePatchCore.cleanupOldEntries(this.rootDir, '', '', 0).catch(
414
+ (error: Object) => {
415
+ console.error('reset cleanup failed:', error);
416
+ },
417
+ );
418
+ this.trace('resetToPackagedBundle:after');
419
+ }
420
+
375
421
  public async downloadFullUpdate(url: string, hash: string): Promise<void> {
376
422
  try {
377
423
  const params = this.createTaskParams(
package/harmony/pushy.har CHANGED
Binary file