Skip to content

Commit 8ff0e82

Browse files
sunnylqmclaude
andcommitted
feat: apply HDIFF13 stream-format bundle patches
hpatch_by_file now sniffs the patch header and dispatches: single format (HDIFFSF20) keeps the existing path byte-for-byte; HDIFF13 (diffStream output, served only on the capability-gated v2 track for large bundles) applies via patch_decompress_with_cache with the same streaming memory profile (decompress cache + IO buffers, old loaded to memory only when <= 8MB). Works transparently under the HBC transform flow since that path also funnels through hpatch_by_file. Tests: stream-format apply and transform+stream e2e added to patch_core_test (fixtures generated by node-hdiffpatch and round-trip verified in JS first). Android prebuilt .so rebuilt for all ABIs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 72a836d commit 8ff0e82

8 files changed

Lines changed: 99 additions & 1 deletion

File tree

android/jni/hpatch.c

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,57 @@ int hpatch_by_mem(const uint8_t* old,size_t oldsize,uint8_t* newBuf,size_t newsi
105105
return hpatch_by_stream(&oldStream,hpatch_FALSE,&patStream,&newStream,patInfo);
106106
}
107107

108+
// HDIFF13(diffStream 产物,v2 轨道的大 bundle patch):流式应用,
109+
// 内存 = 解压缓存 + IO 缓冲(+ old ≤ 8MB 时的整载优化),与 single 路径同级。
110+
static int hpatch_v13_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOldAllToMem,
111+
const hpatch_TStreamInput* pat,hpatch_TStreamOutput* out_new){
112+
int result=kHPatch_ok;
113+
int _isInClear=hpatch_FALSE;
114+
hpatch_TDecompress* decompressPlugin=0;
115+
uint8_t* temp_cache=0;
116+
size_t temp_cache_size;
117+
hpatch_compressedDiffInfo patInfo;
118+
hpatch_TStreamInput _old;
119+
{// info
120+
_check(getCompressedDiffInfo(&patInfo,pat),kHPatch_error_info);
121+
_check(old->streamSize==patInfo.oldDataSize,kHPatch_error_old_size);
122+
_check(out_new->streamSize>=patInfo.newDataSize,kHPatch_error_new_size);
123+
out_new->streamSize=patInfo.newDataSize;
124+
if (strlen(patInfo.compressType)>0){
125+
decompressPlugin=getDecompressPlugin(patInfo.compressType);
126+
_check(decompressPlugin,kHPatch_error_compressType);
127+
}
128+
}
129+
{// mem
130+
size_t mem_size;
131+
size_t oldSize=(size_t)old->streamSize;
132+
isLoadOldAllToMem=isLoadOldAllToMem&&(old->streamSize<=kMaxLoadMemOldSize);
133+
temp_cache_size=hpatch_kStreamCacheSize*8+hpatch_kFileIOBufBetterSize*3;
134+
mem_size=temp_cache_size+(isLoadOldAllToMem?oldSize:0);
135+
temp_cache=malloc(mem_size);
136+
_check(temp_cache,kHPatch_error_malloc);
137+
if (isLoadOldAllToMem){//load old to mem
138+
uint8_t* oldMem=temp_cache+temp_cache_size;
139+
_check(old->read(old,0,oldMem,oldMem+oldSize),kHPatch_error_old_fread);
140+
mem_as_hStreamInput(&_old,oldMem,oldMem+oldSize);
141+
old=&_old;
142+
}
143+
}
144+
145+
_check(patch_decompress_with_cache(out_new,old,pat,decompressPlugin,
146+
temp_cache,temp_cache+temp_cache_size),kHPatch_error_patch);
147+
148+
_clear:
149+
_isInClear=hpatch_TRUE;
150+
if (temp_cache){ free(temp_cache); temp_cache=0; }
151+
return result;
152+
}
153+
108154
int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile){
109155
int result=kHPatch_ok;
110156
int _isInClear=hpatch_FALSE;
111157
int patch_result;
158+
hpatch_singleCompressedDiffInfo singleInfo;
112159
hpatch_TFileStreamInput oldStream;
113160
hpatch_TFileStreamInput patStream;
114161
hpatch_TFileStreamOutput newStream;
@@ -120,7 +167,13 @@ int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfi
120167
_check(hpatch_TFileStreamInput_open(&patStream,patchfile),kHPatch_error_pat_fopen);
121168
_check(hpatch_TFileStreamOutput_open(&newStream,newfile,~(hpatch_StreamPos_t)0),kHPatch_error_new_fopen);
122169

123-
patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,0);
170+
// 按 patch 头自动分派格式:single(HDIFFSF20,现状)或 stream(HDIFF13,
171+
// v2 轨道大 bundle)。老客户端只会收到 single;能力门控在服务端。
172+
if (getSingleCompressedDiffInfo(&singleInfo,&patStream.base,0)){
173+
patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,&singleInfo);
174+
}else{
175+
patch_result=hpatch_v13_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base);
176+
}
124177
if (patch_result!=kHPatch_ok){
125178
_check(!oldStream.fileError,kHPatch_error_old_fread);
126179
_check(!patStream.fileError,kHPatch_error_pat_fread);
7.05 KB
Binary file not shown.
6.77 KB
Binary file not shown.

android/lib/x86/librnupdate.so

9.8 KB
Binary file not shown.

android/lib/x86_64/librnupdate.so

8.08 KB
Binary file not shown.
941 Bytes
Binary file not shown.
760 Bytes
Binary file not shown.

cpp/patch_core/tests/patch_core_test.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,49 @@ void TestApplyPatchWithHbcTransformRejectsBadMeta() {
210210
"error should identify unsupported version: " + status.message);
211211
}
212212

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+
213256
void TestApplyPatchFromFileSourceMergesAndCopies() {
214257
TempDir temp;
215258
const std::string source = JoinPath(temp.path, "origin");
@@ -687,6 +730,8 @@ int main(int argc, char** argv) {
687730
g_fixtures_dir = argv[1];
688731
}
689732
const std::vector<std::pair<std::string, void (*)()>> tests = {
733+
{"ApplyStreamFormatBundlePatch", TestApplyStreamFormatBundlePatch},
734+
{"ApplyStreamFormatWithHbcTransform", TestApplyStreamFormatWithHbcTransform},
690735
{"ApplyPatchWithHbcTransform", TestApplyPatchWithHbcTransform},
691736
{"ApplyPatchWithHbcTransformRejectsBadMeta", TestApplyPatchWithHbcTransformRejectsBadMeta},
692737
{"ApplyPatchFromFileSourceMergesAndCopies", TestApplyPatchFromFileSourceMergesAndCopies},

0 commit comments

Comments
 (0)