react-native-update 10.47.0 → 10.48.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.
Files changed (63) hide show
  1. package/android/jni/Android.mk +2 -0
  2. package/android/jni/hpatch.c +55 -20
  3. package/android/jni/hpatch.h +0 -6
  4. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  5. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  6. package/android/lib/x86/librnupdate.so +0 -0
  7. package/android/lib/x86_64/librnupdate.so +0 -0
  8. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +29 -6
  9. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +2 -0
  10. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +1 -0
  11. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateCore.java +12 -0
  12. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +47 -2
  13. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +12 -3
  14. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +13 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +11 -0
  16. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  17. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  18. package/cpp/patch_core/error_codes.h +3 -0
  19. package/cpp/patch_core/hbc_transform.cpp +147 -0
  20. package/cpp/patch_core/hbc_transform.h +53 -0
  21. package/cpp/patch_core/hbc_transform_wire.cpp +327 -0
  22. package/cpp/patch_core/hbc_transform_wire.h +52 -0
  23. package/cpp/patch_core/patch_core.cpp +126 -4
  24. package/cpp/patch_core/patch_core.h +5 -0
  25. package/cpp/patch_core/patch_core_android.cpp +3 -1
  26. package/cpp/patch_core/tests/fixtures/rawstored.lzma2label.patch.bin +0 -0
  27. package/cpp/patch_core/tests/fixtures/rawstored.new.bin +0 -0
  28. package/cpp/patch_core/tests/fixtures/rawstored.old.bin +0 -0
  29. package/cpp/patch_core/tests/fixtures/v96.hbc +0 -0
  30. package/cpp/patch_core/tests/fixtures/v96.meta.json +1 -0
  31. package/cpp/patch_core/tests/fixtures/v96.streampatch.bin +0 -0
  32. package/cpp/patch_core/tests/fixtures/v96.t.hbc +0 -0
  33. package/cpp/patch_core/tests/fixtures/v96.tpatch.bin +0 -0
  34. package/cpp/patch_core/tests/fixtures/v96.tstreampatch.bin +0 -0
  35. package/cpp/patch_core/tests/fixtures/v96b.hbc +0 -0
  36. package/cpp/patch_core/tests/fixtures/v98.hbc +0 -0
  37. package/cpp/patch_core/tests/fixtures/v98.t.hbc +0 -0
  38. package/cpp/patch_core/tests/fixtures/v98b.hbc +0 -0
  39. package/cpp/patch_core/tests/fixtures/v98b.t.hbc +0 -0
  40. package/cpp/patch_core/tests/hbc_transform_test.cpp +384 -0
  41. package/cpp/patch_core/tests/patch_core_test.cpp +150 -1
  42. package/cpp/patch_core/update_core_android.cpp +10 -0
  43. package/harmony/pushy/src/main/cpp/CMakeLists.txt +33 -39
  44. package/harmony/pushy/src/main/cpp/pushy.cpp +31 -6
  45. package/harmony/pushy/src/main/ets/DownloadTask.ts +34 -2
  46. package/harmony/pushy/src/main/ets/NativePatchCore.ts +4 -0
  47. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +12 -0
  48. package/harmony/pushy/src/main/ets/UpdateContext.ts +79 -2
  49. package/harmony/pushy.har +0 -0
  50. package/ios/RCTPushy/RCTPushy.mm +93 -1
  51. package/ios/RCTPushy/RCTPushyDownloader.mm +11 -2
  52. package/package.json +1 -1
  53. package/react-native-update.podspec +2 -0
  54. package/src/NativePushy.ts +1 -0
  55. package/src/client.ts +167 -38
  56. package/src/context.ts +4 -0
  57. package/src/core.ts +4 -0
  58. package/src/error.ts +51 -5
  59. package/src/locales/en.ts +2 -0
  60. package/src/locales/zh.ts +2 -0
  61. package/src/provider.tsx +32 -15
  62. package/src/telemetry.ts +6 -1
  63. package/src/type.ts +3 -0
@@ -21,6 +21,8 @@ Hdp_Files := \
21
21
 
22
22
  LOCAL_SRC_FILES := \
23
23
  ../../cpp/patch_core/archive_patch_core.cpp \
24
+ ../../cpp/patch_core/hbc_transform.cpp \
25
+ ../../cpp/patch_core/hbc_transform_wire.cpp \
24
26
  ../../cpp/patch_core/patch_core.cpp \
25
27
  ../../cpp/patch_core/patch_core_android.cpp \
26
28
  ../../cpp/patch_core/state_core.cpp \
@@ -18,15 +18,6 @@
18
18
  #define _check(v,errorType) do{ \
19
19
  if (!(v)){ if (result==kHPatch_ok) result=errorType; if (!_isInClear){ goto _clear; }; } }while(0)
20
20
 
21
- int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
22
- const uint8_t* pat,size_t patsize){
23
- hpatch_TStreamInput patStream;
24
- mem_as_hStreamInput(&patStream,pat,pat+patsize);
25
- if (!getSingleCompressedDiffInfo(out_patinfo,&patStream,0))
26
- return kHPatch_error_info;//data error;
27
- return kHPatch_ok; //ok
28
- }
29
-
30
21
  static hpatch_TDecompress* getDecompressPlugin(const char* compressType){
31
22
  #ifdef _CompressPlugin_zlib
32
23
  if (zlibDecompressPlugin.is_can_open(compressType))
@@ -63,7 +54,9 @@ static int hpatch_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOld
63
54
  _check(old->streamSize==patInfo->oldDataSize,kHPatch_error_old_size);
64
55
  _check(out_new->streamSize>=patInfo->newDataSize,kHPatch_error_new_size);
65
56
  out_new->streamSize=patInfo->newDataSize;
66
- if (strlen(patInfo->compressType)>0){
57
+ // v5 writer 可能保留 lzma2 标签但以 RAW 存储小 diff;compressedSize==0
58
+ // 才是格式里的未压缩判据,不能按标签强行走解压。
59
+ if ((strlen(patInfo->compressType)>0)&&(patInfo->compressedSize>0)){
67
60
  decompressPlugin=getDecompressPlugin(patInfo->compressType);
68
61
  _check(decompressPlugin,kHPatch_error_compressType);
69
62
  }
@@ -94,21 +87,57 @@ _clear:
94
87
  return result;
95
88
  }
96
89
 
97
- int hpatch_by_mem(const uint8_t* old,size_t oldsize,uint8_t* newBuf,size_t newsize,
98
- const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo){
99
- hpatch_TStreamInput oldStream;
100
- hpatch_TStreamInput patStream;
101
- hpatch_TStreamOutput newStream;
102
- mem_as_hStreamInput(&oldStream,old,old+oldsize);
103
- mem_as_hStreamInput(&patStream,pat,pat+patsize);
104
- mem_as_hStreamOutput(&newStream,newBuf,newBuf+newsize);
105
- return hpatch_by_stream(&oldStream,hpatch_FALSE,&patStream,&newStream,patInfo);
90
+ // HDIFF13(diffStream 产物,v2 轨道的大 bundle patch):流式应用,
91
+ // 内存 = 解压缓存 + IO 缓冲(+ old ≤ 8MB 时的整载优化),与 single 路径同级。
92
+ static int hpatch_v13_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOldAllToMem,
93
+ const hpatch_TStreamInput* pat,hpatch_TStreamOutput* out_new){
94
+ int result=kHPatch_ok;
95
+ int _isInClear=hpatch_FALSE;
96
+ hpatch_TDecompress* decompressPlugin=0;
97
+ uint8_t* temp_cache=0;
98
+ size_t temp_cache_size;
99
+ hpatch_compressedDiffInfo patInfo;
100
+ hpatch_TStreamInput _old;
101
+ {// info
102
+ _check(getCompressedDiffInfo(&patInfo,pat),kHPatch_error_info);
103
+ _check(old->streamSize==patInfo.oldDataSize,kHPatch_error_old_size);
104
+ _check(out_new->streamSize>=patInfo.newDataSize,kHPatch_error_new_size);
105
+ out_new->streamSize=patInfo.newDataSize;
106
+ if (strlen(patInfo.compressType)>0){
107
+ decompressPlugin=getDecompressPlugin(patInfo.compressType);
108
+ _check(decompressPlugin,kHPatch_error_compressType);
109
+ }
110
+ }
111
+ {// mem
112
+ size_t mem_size;
113
+ size_t oldSize=(size_t)old->streamSize;
114
+ isLoadOldAllToMem=isLoadOldAllToMem&&(old->streamSize<=kMaxLoadMemOldSize);
115
+ temp_cache_size=hpatch_kStreamCacheSize*8+hpatch_kFileIOBufBetterSize*3;
116
+ mem_size=temp_cache_size+(isLoadOldAllToMem?oldSize:0);
117
+ temp_cache=malloc(mem_size);
118
+ _check(temp_cache,kHPatch_error_malloc);
119
+ if (isLoadOldAllToMem){//load old to mem
120
+ uint8_t* oldMem=temp_cache+temp_cache_size;
121
+ _check(old->read(old,0,oldMem,oldMem+oldSize),kHPatch_error_old_fread);
122
+ mem_as_hStreamInput(&_old,oldMem,oldMem+oldSize);
123
+ old=&_old;
124
+ }
125
+ }
126
+
127
+ _check(patch_decompress_with_cache(out_new,old,pat,decompressPlugin,
128
+ temp_cache,temp_cache+temp_cache_size),kHPatch_error_patch);
129
+
130
+ _clear:
131
+ _isInClear=hpatch_TRUE;
132
+ if (temp_cache){ free(temp_cache); temp_cache=0; }
133
+ return result;
106
134
  }
107
135
 
108
136
  int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile){
109
137
  int result=kHPatch_ok;
110
138
  int _isInClear=hpatch_FALSE;
111
139
  int patch_result;
140
+ hpatch_singleCompressedDiffInfo singleInfo;
112
141
  hpatch_TFileStreamInput oldStream;
113
142
  hpatch_TFileStreamInput patStream;
114
143
  hpatch_TFileStreamOutput newStream;
@@ -120,7 +149,13 @@ int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfi
120
149
  _check(hpatch_TFileStreamInput_open(&patStream,patchfile),kHPatch_error_pat_fopen);
121
150
  _check(hpatch_TFileStreamOutput_open(&newStream,newfile,~(hpatch_StreamPos_t)0),kHPatch_error_new_fopen);
122
151
 
123
- patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,0);
152
+ // 按 patch 头自动分派格式:single(HDIFFSF20,现状)或 stream(HDIFF13,
153
+ // v2 轨道大 bundle)。老客户端只会收到 single;能力门控在服务端。
154
+ if (getSingleCompressedDiffInfo(&singleInfo,&patStream.base,0)){
155
+ patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,&singleInfo);
156
+ }else{
157
+ patch_result=hpatch_v13_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base);
158
+ }
124
159
  if (patch_result!=kHPatch_ok){
125
160
  _check(!oldStream.fileError,kHPatch_error_old_fread);
126
161
  _check(!patStream.fileError,kHPatch_error_pat_fread);
@@ -30,12 +30,6 @@ enum {
30
30
  kHPatch_error_new_size =-15,
31
31
  };
32
32
 
33
- int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
34
- const uint8_t* pat,size_t patsize);
35
-
36
- //patInfo can NULL
37
- int hpatch_by_mem(const uint8_t* old,size_t oldsize, uint8_t* newBuf,size_t newsize,
38
- const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo);
39
33
  int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile);
40
34
 
41
35
  #ifdef __cplusplus
Binary file
Binary file
@@ -53,6 +53,17 @@ class DownloadTask implements Runnable {
53
53
  final ArrayList<String> copyFroms = new ArrayList<String>();
54
54
  final ArrayList<String> copyTos = new ArrayList<String>();
55
55
  final ArrayList<String> deletes = new ArrayList<String>();
56
+ // __diff.json 的 hbcTransform 元数据(按 patch 条目名索引);
57
+ // 为空对象/缺失时返回 ""(native 走现状路径)
58
+ JSONObject hbcTransform;
59
+
60
+ String hbcTransformMetaFor(String patchEntryName) {
61
+ if (hbcTransform == null) {
62
+ return "";
63
+ }
64
+ JSONObject meta = hbcTransform.optJSONObject(patchEntryName);
65
+ return meta != null ? meta.toString() : "";
66
+ }
56
67
  // Maps a copy source path ("from") to the CRC32 of the file content,
57
68
  // when provided by the manifest ("copiesCrc"). Lets the resource
58
69
  // copier locate the file by content if the path is not present on
@@ -74,12 +85,15 @@ class DownloadTask implements Runnable {
74
85
  }
75
86
 
76
87
  private void postProgress(final long received, final long total) {
88
+ // Cross-platform progress contract: unknown length is reported as
89
+ // total=0, never a raw -1 (OkHttp's contentLength for chunked/gzip).
90
+ final long normalizedTotal = total > 0 ? total : 0;
77
91
  mainHandler.post(new Runnable() {
78
92
  @Override
79
93
  public void run() {
80
94
  WritableMap progress = Arguments.createMap();
81
95
  progress.putDouble("received", received);
82
- progress.putDouble("total", total);
96
+ progress.putDouble("total", normalizedTotal);
83
97
  progress.putString("hash", hash);
84
98
  UpdateEventEmitter.sendEvent("RCTPushyDownloadProgress", progress);
85
99
  }
@@ -125,6 +139,7 @@ class DownloadTask implements Runnable {
125
139
  int percentage = (int) (received * 100.0 / contentLength + 0.5);
126
140
  if (percentage > currentPercentage) {
127
141
  currentPercentage = percentage;
142
+ lastPostedBytes = received;
128
143
  postProgress(received, contentLength);
129
144
  }
130
145
  } else if (received - lastPostedBytes >= PROGRESS_BYTES_THRESHOLD) {
@@ -138,7 +153,11 @@ class DownloadTask implements Runnable {
138
153
  if (contentLength >= 0 && received != contentLength) {
139
154
  throw new IOException("Unexpected eof while reading downloaded update");
140
155
  }
141
- postProgress(received, contentLength);
156
+ // Final progress event, skipped when the loop already posted this
157
+ // exact value (known length reaching 100% posts it in-loop).
158
+ if (received != lastPostedBytes) {
159
+ postProgress(received, contentLength);
160
+ }
142
161
  }
143
162
 
144
163
  }
@@ -251,6 +270,7 @@ class DownloadTask implements Runnable {
251
270
  contents.deletes,
252
271
  contents.copyCrcs
253
272
  );
273
+ contents.hbcTransform = manifest.optJSONObject("hbcTransform");
254
274
  continue;
255
275
  }
256
276
 
@@ -311,7 +331,8 @@ class DownloadTask implements Runnable {
311
331
  false,
312
332
  new String[0],
313
333
  new String[0],
314
- new String[0]
334
+ new String[0],
335
+ contents.hbcTransformMetaFor("index.bundlejs.patch")
315
336
  );
316
337
  } finally {
317
338
  originBundleFile.delete();
@@ -345,7 +366,8 @@ class DownloadTask implements Runnable {
345
366
  plan.enableMerge,
346
367
  contents.copyFroms.toArray(new String[0]),
347
368
  contents.copyTos.toArray(new String[0]),
348
- contents.deletes.toArray(new String[0])
369
+ contents.deletes.toArray(new String[0]),
370
+ contents.hbcTransformMetaFor("index.bundlejs.patch")
349
371
  );
350
372
  if (params.targetFile.exists()) {
351
373
  params.targetFile.delete();
@@ -357,7 +379,7 @@ class DownloadTask implements Runnable {
357
379
  params.unzipDirectory.getAbsolutePath(),
358
380
  params.hash,
359
381
  params.originHash,
360
- 3
382
+ params.maxAgeDays
361
383
  );
362
384
  }
363
385
 
@@ -444,7 +466,8 @@ class DownloadTask implements Runnable {
444
466
  boolean enableMerge,
445
467
  String[] copyFroms,
446
468
  String[] copyTos,
447
- String[] deletes
469
+ String[] deletes,
470
+ String hbcTransformMeta
448
471
  );
449
472
 
450
473
  private static native void cleanupOldEntries(
@@ -18,6 +18,8 @@ class DownloadTaskParams {
18
18
  String url;
19
19
  String hash;
20
20
  String originHash;
21
+ // TASK_TYPE_CLEANUP only: entries younger than this survive; 0 = delete all
22
+ int maxAgeDays = 3;
21
23
  File targetFile;
22
24
  File unzipDirectory;
23
25
  File originDirectory;
@@ -16,6 +16,7 @@ final class ErrorCodes {
16
16
  static final String SWITCH_VERSION_FAILED = "SWITCH_VERSION_FAILED";
17
17
  static final String MARK_SUCCESS_FAILED = "MARK_SUCCESS_FAILED";
18
18
  static final String RESTART_FAILED = "RESTART_FAILED";
19
+ static final String RESET_FAILED = "RESET_FAILED";
19
20
  static final String INVALID_HASH_INFO = "INVALID_HASH_INFO";
20
21
  static final String UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM";
21
22
 
@@ -23,4 +23,16 @@ final class NativeUpdateCore {
23
23
 
24
24
  loaded = true;
25
25
  }
26
+
27
+ /**
28
+ * 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道)。
29
+ * 经 getConstants 暴露给 JS,再随 checkUpdate 以 diffV 上报,
30
+ * 服务端按能力门控下发。
31
+ */
32
+ static int supportedDiffVersion() {
33
+ ensureLoaded();
34
+ return getSupportedDiffVersion();
35
+ }
36
+
37
+ private static native int getSupportedDiffVersion();
26
38
  }
@@ -26,8 +26,16 @@ public class UpdateContext {
26
26
  private final SharedPreferences sp;
27
27
 
28
28
  private ReactInstanceManager reactInstanceManager;
29
- private boolean isUsingBundleUrl;
30
- private boolean ignoreRollback;
29
+ // Written on the launch path, read from executor/JS threads.
30
+ private volatile boolean isUsingBundleUrl;
31
+ private volatile boolean ignoreRollback;
32
+ // The version whose bundle this process actually loaded (resolved in
33
+ // getBundleUrl). resetToPackagedBundle must not delete its directory:
34
+ // update assets (images/fonts) are read from it on demand at runtime, so
35
+ // wiping it under a silent (no-restart) reset would break every image the
36
+ // running app has not loaded yet. Volatile: written during launch, read
37
+ // from the state serial executor.
38
+ private volatile String launchVersion;
31
39
  private static final int STATE_OP_SWITCH_VERSION = 1;
32
40
  private static final int STATE_OP_MARK_SUCCESS = 2;
33
41
  private static final int STATE_OP_ROLLBACK = 3;
@@ -303,6 +311,42 @@ public class UpdateContext {
303
311
  this.cleanUp();
304
312
  }
305
313
 
314
+ /**
315
+ * Reset to the bundle packaged in the binary: wipe the whole update state
316
+ * (so the next launch resolves to the built-in bundle) and delete the
317
+ * downloaded versions, keeping only the directory of the version this
318
+ * process is running from (a silent reset must not break its on-demand
319
+ * asset loads). Only the client uuid survives — it identifies the install
320
+ * for gray release bucketing and must not change on reset.
321
+ */
322
+ public void resetToPackagedBundle() {
323
+ StateCoreResult resetState = new StateCoreResult();
324
+ resetState.packageVersion = getPackageVersion();
325
+ resetState.buildTime = getBuildTime();
326
+ resetState.firstTime = false;
327
+ resetState.firstTimeOk = true;
328
+ String uuid = sp.getString("uuid", null);
329
+ SharedPreferences.Editor editor = sp.edit();
330
+ editor.clear();
331
+ applyState(editor, resetState);
332
+ if (uuid != null) {
333
+ editor.putString("uuid", uuid);
334
+ }
335
+ persistEditor(editor, "reset to packaged bundle");
336
+ ignoreRollback = false;
337
+ Log.i(TAG, "Reset to packaged bundle");
338
+
339
+ DownloadTaskParams params = new DownloadTaskParams();
340
+ params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
341
+ params.maxAgeDays = 0;
342
+ // Keep the directory of the version this process is running from (a
343
+ // silent reset would otherwise break its on-demand asset loads); the
344
+ // orphaned directory is removed by the next regular cleanup.
345
+ params.hash = launchVersion;
346
+ params.unzipDirectory = rootDir;
347
+ enqueue(params);
348
+ }
349
+
306
350
  public void clearRollbackMark() {
307
351
  StateCoreResult currentState = getStateSnapshot();
308
352
  StateCoreResult nextState = runStateCore(
@@ -405,6 +449,7 @@ public class UpdateContext {
405
449
  currentVersion = this.rollBack();
406
450
  continue;
407
451
  }
452
+ launchVersion = currentVersion;
408
453
  return bundleFile.toString();
409
454
  }
410
455
 
@@ -1,5 +1,6 @@
1
1
  package cn.reactnative.modules.update;
2
2
 
3
+ import android.util.Log;
3
4
  import androidx.annotation.Nullable;
4
5
  import com.facebook.react.bridge.ReactApplicationContext;
5
6
  import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -28,9 +29,17 @@ final class UpdateEventEmitter {
28
29
  return;
29
30
  }
30
31
 
31
- reactContext
32
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
33
- .emit(eventName, params);
32
+ try {
33
+ reactContext
34
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
35
+ .emit(eventName, params);
36
+ } catch (RuntimeException e) {
37
+ // The instance can be torn down between the check above and the
38
+ // emit (this SDK itself triggers reloads); sendEvent runs on the
39
+ // main thread, so a throw here would crash the app for a lost
40
+ // progress tick.
41
+ Log.w("pushy", "sendEvent " + eventName + " failed: " + e.getMessage());
42
+ }
34
43
  }
35
44
 
36
45
  @SuppressWarnings("deprecation")
@@ -191,6 +191,19 @@ public class UpdateModuleImpl {
191
191
  });
192
192
  }
193
193
 
194
+ public static void resetToPackagedBundle(
195
+ final UpdateContext updateContext,
196
+ final Promise promise
197
+ ) {
198
+ StateSerialRunner.run(promise, ErrorCodes.RESET_FAILED, "resetToPackagedBundle", new StateSerialRunner.Operation() {
199
+ @Override
200
+ public void run() {
201
+ updateContext.resetToPackagedBundle();
202
+ promise.resolve(true);
203
+ }
204
+ });
205
+ }
206
+
194
207
  private static void setUuidInternal(UpdateContext updateContext, String uuid) {
195
208
  updateContext.setKv("uuid", uuid);
196
209
  }
@@ -5,6 +5,7 @@ import static androidx.core.content.FileProvider.getUriForFile;
5
5
  import android.content.Intent;
6
6
  import android.net.Uri;
7
7
  import android.os.Build;
8
+ import android.util.Log;
8
9
  import com.facebook.react.bridge.ReactApplicationContext;
9
10
  import java.io.File;
10
11
  import java.util.HashMap;
@@ -35,6 +36,16 @@ final class UpdateModuleSupport {
35
36
  }
36
37
 
37
38
  constants.put("uuid", updateContext.getKv("uuid"));
39
+ int supportedDiffVersion = 0;
40
+ try {
41
+ supportedDiffVersion = NativeUpdateCore.supportedDiffVersion();
42
+ } catch (UnsatisfiedLinkError e) {
43
+ // A mismatched librnupdate.so (stale manual copy / build cache)
44
+ // must not crash startup via getConstants; 0 simply means "no v2
45
+ // diff track" and the server degrades gracefully.
46
+ Log.e("pushy", "supportedDiffVersion missing from librnupdate.so", e);
47
+ }
48
+ constants.put("supportedDiffVersion", supportedDiffVersion);
38
49
  return constants;
39
50
  }
40
51
 
@@ -73,6 +73,11 @@ public class UpdateModule extends NativePushySpec {
73
73
  UpdateModuleImpl.markSuccess(updateContext, promise);
74
74
  }
75
75
 
76
+ @Override
77
+ public void resetToPackagedBundle(Promise promise) {
78
+ UpdateModuleImpl.resetToPackagedBundle(updateContext, promise);
79
+ }
80
+
76
81
  @Override
77
82
  public void setUuid(String uuid, Promise promise) {
78
83
  UpdateModuleImpl.setUuid(updateContext, uuid, promise);
@@ -75,6 +75,11 @@ public class UpdateModule extends ReactContextBaseJavaModule {
75
75
  UpdateModuleImpl.markSuccess(updateContext);
76
76
  }
77
77
 
78
+ @ReactMethod
79
+ public void resetToPackagedBundle(Promise promise) {
80
+ UpdateModuleImpl.resetToPackagedBundle(updateContext, promise);
81
+ }
82
+
78
83
  @ReactMethod
79
84
  public void setUuid(String uuid) {
80
85
  UpdateModuleImpl.setUuid(updateContext, uuid);
@@ -31,6 +31,9 @@ constexpr const char* kSwitchVersionFailed = "SWITCH_VERSION_FAILED";
31
31
  constexpr const char* kMarkSuccessFailed = "MARK_SUCCESS_FAILED";
32
32
  // reloadUpdate / restartApp failed.
33
33
  constexpr const char* kRestartFailed = "RESTART_FAILED";
34
+ // resetToPackagedBundle failed (state wipe or cleanup could not run), or the
35
+ // installed native module predates the method (JS-layer detection).
36
+ constexpr const char* kResetFailed = "RESET_FAILED";
34
37
  // Stored or provided hash info is not a valid JSON object.
35
38
  constexpr const char* kInvalidHashInfo = "INVALID_HASH_INFO";
36
39
  // The method is not supported on this platform (e.g. downloadAndInstallApk
@@ -0,0 +1,147 @@
1
+ #include "hbc_transform.h"
2
+
3
+ #include <cstring>
4
+
5
+ namespace pushy {
6
+ namespace hbc {
7
+
8
+ namespace {
9
+
10
+ constexpr uint8_t kHbcMagic[8] =
11
+ {0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f};
12
+ constexpr size_t kHeaderSize = 128;
13
+ constexpr size_t kCountsOffset = 32; // magic(8) + version(4) + sourceHash(20)
14
+ constexpr uint32_t kMaxEntryCount = 0x0fffffff;
15
+ constexpr uint32_t kMaxEntrySize = 4096;
16
+ constexpr uint32_t kMaxSections = 64;
17
+ constexpr uint32_t kMaxCountFields = (kHeaderSize - kCountsOffset) / 4; // 24
18
+
19
+ inline uint32_t ReadU32(const uint8_t* p) {
20
+ return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) |
21
+ (static_cast<uint32_t>(p[2]) << 16) |
22
+ (static_cast<uint32_t>(p[3]) << 24);
23
+ }
24
+
25
+ inline void WriteU32(uint8_t* p, uint32_t v) {
26
+ p[0] = static_cast<uint8_t>(v);
27
+ p[1] = static_cast<uint8_t>(v >> 8);
28
+ p[2] = static_cast<uint8_t>(v >> 16);
29
+ p[3] = static_cast<uint8_t>(v >> 24);
30
+ }
31
+
32
+ inline uint64_t Align4(uint64_t x) {
33
+ return (x + 3) & ~static_cast<uint64_t>(3);
34
+ }
35
+
36
+ struct ResolvedSection {
37
+ uint64_t start;
38
+ uint64_t size;
39
+ const HbcSectionDesc* desc;
40
+ };
41
+
42
+ } // namespace
43
+
44
+ bool TransformHbcInPlace(
45
+ uint8_t* data,
46
+ size_t size,
47
+ const HbcLayoutDesc& layout,
48
+ bool inverse) {
49
+ // ---- 校验阶段:改写任何字节之前完成全部检查 ----
50
+ if (data == nullptr || size < kHeaderSize) {
51
+ return false;
52
+ }
53
+ if (std::memcmp(data, kHbcMagic, sizeof(kHbcMagic)) != 0) {
54
+ return false;
55
+ }
56
+ if (layout.headerCountFields < 2 ||
57
+ layout.headerCountFields > kMaxCountFields) {
58
+ return false;
59
+ }
60
+ if (layout.sections == nullptr || layout.sectionCount == 0 ||
61
+ layout.sectionCount > kMaxSections) {
62
+ return false;
63
+ }
64
+
65
+ uint32_t counts[kMaxCountFields];
66
+ for (uint32_t i = 0; i < layout.headerCountFields; ++i) {
67
+ counts[i] = ReadU32(data + kCountsOffset + static_cast<size_t>(i) * 4);
68
+ }
69
+ // 位置约定:槽位 0 = fileLength,最后一个槽位 = debugInfoOffset
70
+ const uint64_t fileLength = counts[0];
71
+ const uint64_t debugInfoOffset = counts[layout.headerCountFields - 1];
72
+ if (fileLength != size) {
73
+ return false;
74
+ }
75
+ if (debugInfoOffset < kHeaderSize || debugInfoOffset > size) {
76
+ return false;
77
+ }
78
+
79
+ ResolvedSection resolved[kMaxSections];
80
+ uint64_t off = kHeaderSize;
81
+ for (uint32_t i = 0; i < layout.sectionCount; ++i) {
82
+ const HbcSectionDesc& s = layout.sections[i];
83
+ if (s.countIndex >= layout.headerCountFields) {
84
+ return false;
85
+ }
86
+ if (s.entrySize == 0 || s.entrySize > kMaxEntrySize) {
87
+ return false;
88
+ }
89
+ const uint64_t count = counts[s.countIndex];
90
+ if (count > kMaxEntryCount) {
91
+ return false;
92
+ }
93
+ if (s.deltaFieldCount > 0 && s.deltaFields == nullptr) {
94
+ return false;
95
+ }
96
+ for (uint32_t f = 0; f < s.deltaFieldCount; ++f) {
97
+ const HbcDeltaField& field = s.deltaFields[f];
98
+ // bit 必须单独设上界:bit=0xFFFFFFFF 时 bit+bits 的 uint32 求和会回绕
99
+ // 绕过 >32 检查,改写阶段的 << bit 就成了移位量 ≥32 的 UB。
100
+ // bits≥1 时合法 bit 必 ≤31。
101
+ if (field.bits < 1 || field.bits > 32 || field.bit > 31 ||
102
+ field.bit + field.bits > 32 ||
103
+ static_cast<uint64_t>(field.byte) + 4 > s.entrySize) {
104
+ return false;
105
+ }
106
+ }
107
+ const uint64_t sectionSize = count * s.entrySize; // ≤ 2^28 × 2^12 < 2^40
108
+ off = Align4(off);
109
+ if (off + sectionSize > debugInfoOffset) {
110
+ return false;
111
+ }
112
+ resolved[i] = {off, sectionSize, &s};
113
+ off += sectionSize;
114
+ }
115
+
116
+ // ---- 改写阶段:校验通过后不再有失败路径 ----
117
+ for (uint32_t i = 0; i < layout.sectionCount; ++i) {
118
+ const ResolvedSection& r = resolved[i];
119
+ const HbcSectionDesc& s = *r.desc;
120
+ for (uint32_t f = 0; f < s.deltaFieldCount; ++f) {
121
+ const HbcDeltaField& field = s.deltaFields[f];
122
+ const uint32_t fieldMask =
123
+ field.bits == 32 ? 0xffffffffu : ((1u << field.bits) - 1u);
124
+ const uint32_t mask = fieldMask << field.bit;
125
+ uint32_t prev = 0;
126
+ const uint64_t end = r.start + r.size;
127
+ for (uint64_t p = r.start + field.byte; p < end; p += s.entrySize) {
128
+ uint8_t* wordPtr = data + p;
129
+ const uint32_t word = ReadU32(wordPtr);
130
+ const uint32_t val = (word >> field.bit) & fieldMask;
131
+ uint32_t enc;
132
+ if (!inverse) {
133
+ enc = (val - prev) & fieldMask;
134
+ prev = val;
135
+ } else {
136
+ enc = (val + prev) & fieldMask;
137
+ prev = enc;
138
+ }
139
+ WriteU32(wordPtr, (word & ~mask) | (enc << field.bit));
140
+ }
141
+ }
142
+ }
143
+ return true;
144
+ }
145
+
146
+ } // namespace hbc
147
+ } // namespace pushy
@@ -0,0 +1,53 @@
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+
6
+ namespace pushy {
7
+ namespace hbc {
8
+
9
+ // Hermes 字节码(HBC)delta-friendly 可逆变换的通用解释器。
10
+ //
11
+ // 布局描述表来自 patch 的 __diff.json 元数据(生成端 CLI 写入),本解释器
12
+ // 不含任何 HBC 版本分支——Hermes 版本演进只需要生成端更新描述表,客户端
13
+ // 零跟进。描述表被视为不可信输入:所有段边界、条目大小、位域范围在改写
14
+ // 任何字节之前完成全量校验;校验失败时 buffer 保持原样。
15
+ //
16
+ // 变换语义与生成端(react-native-update-cli src/utils/hbcTransform.ts)
17
+ // 严格一致:对描述的偏移位域做前项差分(wrapping,模字段位宽)。
18
+ // wrapping 保证与数据单调性无关的严格可逆。
19
+
20
+ struct HbcDeltaField {
21
+ // 条目内字节偏移处按小端读取 u32,取 [bit, bit+bits) 位做差分
22
+ uint32_t byte;
23
+ uint32_t bit;
24
+ uint32_t bits;
25
+ };
26
+
27
+ struct HbcSectionDesc {
28
+ // 段大小 = headerCounts[countIndex] × entrySize(字节段 entrySize 为 1)
29
+ uint32_t countIndex;
30
+ uint32_t entrySize;
31
+ const HbcDeltaField* deltaFields;
32
+ uint32_t deltaFieldCount;
33
+ };
34
+
35
+ // 与 wire 格式的位置约定一致:counts 槽位 0 = fileLength,
36
+ // 最后一个槽位 = debugInfoOffset(结构校验依赖)。
37
+ struct HbcLayoutDesc {
38
+ uint32_t headerCountFields;
39
+ const HbcSectionDesc* sections;
40
+ uint32_t sectionCount;
41
+ };
42
+
43
+ // 对 data 原地执行变换(inverse=false)或逆变换(inverse=true)。
44
+ // 返回 false 表示 data 不是该描述表下结构合法的 HBC(或描述表本身非法),
45
+ // 此时 data 未被修改。
46
+ bool TransformHbcInPlace(
47
+ uint8_t* data,
48
+ size_t size,
49
+ const HbcLayoutDesc& layout,
50
+ bool inverse);
51
+
52
+ } // namespace hbc
53
+ } // namespace pushy