@hot-updater/bsdiff 0.25.13

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 (45) hide show
  1. package/LICENSE +33 -0
  2. package/assets/hdiff.wasm +0 -0
  3. package/dist/bun.d.ts +3 -0
  4. package/dist/bun.js +19 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/deno.d.ts +3 -0
  7. package/dist/deno.js +20 -0
  8. package/dist/deno.js.map +1 -0
  9. package/dist/errors.d.ts +9 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +13 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/hdiff.d.ts +7 -0
  14. package/dist/hdiff.d.ts.map +1 -0
  15. package/dist/hdiff.js +18 -0
  16. package/dist/hdiff.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.js +5 -0
  19. package/dist/internal/bsdiff.d.ts +6 -0
  20. package/dist/internal/bsdiff.d.ts.map +1 -0
  21. package/dist/internal/bsdiff.js +103 -0
  22. package/dist/internal/bsdiff.js.map +1 -0
  23. package/dist/internal/bytes.d.ts +8 -0
  24. package/dist/internal/bytes.d.ts.map +1 -0
  25. package/dist/internal/bytes.js +18 -0
  26. package/dist/internal/bytes.js.map +1 -0
  27. package/dist/internal/hermes-validate.d.ts +8 -0
  28. package/dist/internal/hermes-validate.d.ts.map +1 -0
  29. package/dist/internal/hermes-validate.js +84 -0
  30. package/dist/internal/hermes-validate.js.map +1 -0
  31. package/dist/node.d.ts +3 -0
  32. package/dist/node.js +19 -0
  33. package/dist/node.js.map +1 -0
  34. package/dist/precompiled.d.ts +5 -0
  35. package/dist/precompiled.d.ts.map +1 -0
  36. package/dist/precompiled.js +11 -0
  37. package/dist/precompiled.js.map +1 -0
  38. package/dist/worker.d.ts +3 -0
  39. package/dist/worker.js +11 -0
  40. package/dist/worker.js.map +1 -0
  41. package/package.json +73 -0
  42. package/rust/hdiff-wasm/Cargo.toml +18 -0
  43. package/rust/hdiff-wasm/src/lib.rs +380 -0
  44. package/scripts/wasm-build/build-wasm.mjs +111 -0
  45. package/scripts/wasm-build/config.json +11 -0
package/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Sungyu Kang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ ADDITIONAL DISCLAIMER
26
+
27
+ The authors of this software shall not be held responsible for any legal issues,
28
+ regulatory violations, policy violations, or disputes arising from the use,
29
+ distribution, or integration of this software.
30
+
31
+ Users are solely responsible for ensuring compliance with all applicable laws,
32
+ regulations, and third-party policies (including but not limited to app store
33
+ policies).
Binary file
package/dist/bun.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { HdiffError, HdiffErrorCode } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ export { HdiffError, type HdiffErrorCode, hdiff };
package/dist/bun.js ADDED
@@ -0,0 +1,19 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ import { installPrecompiledWasm } from "./precompiled.js";
4
+ import { readFileSync } from "node:fs";
5
+
6
+ //#region src/bun.ts
7
+ const HDIFF_WASM_URL = new URL("../assets/hdiff.wasm", import.meta.url);
8
+ installPrecompiledWasm(loadBunWasmModule());
9
+ function loadBunWasmModule() {
10
+ try {
11
+ return new WebAssembly.Module(readFileSync(HDIFF_WASM_URL));
12
+ } catch {
13
+ throw new HdiffError("PATCH_FAILED", `Failed to load hdiff.wasm for Bun runtime: ${HDIFF_WASM_URL.toString()}`);
14
+ }
15
+ }
16
+
17
+ //#endregion
18
+ export { HdiffError, hdiff };
19
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","names":[],"sources":["../src/bun.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { HdiffError, type HdiffErrorCode } from \"./errors.js\";\nimport { hdiff } from \"./hdiff.js\";\nimport { installPrecompiledWasm } from \"./precompiled.js\";\n\nconst HDIFF_WASM_URL = new URL(\"../assets/hdiff.wasm\", import.meta.url);\n\ninstallPrecompiledWasm(loadBunWasmModule());\n\nfunction loadBunWasmModule(): WebAssembly.Module {\n try {\n return new WebAssembly.Module(readFileSync(HDIFF_WASM_URL));\n } catch {\n throw new HdiffError(\n \"PATCH_FAILED\",\n `Failed to load hdiff.wasm for Bun runtime: ${HDIFF_WASM_URL.toString()}`,\n );\n }\n}\n\nexport { hdiff, HdiffError, type HdiffErrorCode };\n"],"mappings":";;;;;;AAKA,MAAM,iBAAiB,IAAI,IAAI,wBAAwB,OAAO,KAAK,IAAI;AAEvE,uBAAuB,mBAAmB,CAAC;AAE3C,SAAS,oBAAwC;AAC/C,KAAI;AACF,SAAO,IAAI,YAAY,OAAO,aAAa,eAAe,CAAC;SACrD;AACN,QAAM,IAAI,WACR,gBACA,8CAA8C,eAAe,UAAU,GACxE"}
package/dist/deno.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { HdiffError, HdiffErrorCode } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ export { HdiffError, type HdiffErrorCode, hdiff };
package/dist/deno.js ADDED
@@ -0,0 +1,20 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ import { installPrecompiledWasm } from "./precompiled.js";
4
+
5
+ //#region src/deno.ts
6
+ const HDIFF_WASM_URL = new URL("../assets/hdiff.wasm", import.meta.url);
7
+ installPrecompiledWasm(loadDenoWasmModule());
8
+ function loadDenoWasmModule() {
9
+ const deno = globalThis.Deno;
10
+ if (!deno || typeof deno.readFileSync !== "function") throw new HdiffError("PATCH_FAILED", "Deno runtime does not expose Deno.readFileSync");
11
+ try {
12
+ return new WebAssembly.Module(deno.readFileSync(HDIFF_WASM_URL));
13
+ } catch {
14
+ throw new HdiffError("PATCH_FAILED", `Failed to load hdiff.wasm for Deno runtime: ${HDIFF_WASM_URL.toString()}`);
15
+ }
16
+ }
17
+
18
+ //#endregion
19
+ export { HdiffError, hdiff };
20
+ //# sourceMappingURL=deno.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno.js","names":[],"sources":["../src/deno.ts"],"sourcesContent":["import { HdiffError, type HdiffErrorCode } from \"./errors.js\";\nimport { hdiff } from \"./hdiff.js\";\nimport { installPrecompiledWasm } from \"./precompiled.js\";\n\nconst HDIFF_WASM_URL = new URL(\"../assets/hdiff.wasm\", import.meta.url);\n\ninstallPrecompiledWasm(loadDenoWasmModule());\n\nfunction loadDenoWasmModule(): WebAssembly.Module {\n const deno = (\n globalThis as {\n Deno?: { readFileSync?: (path: string | URL) => Uint8Array };\n }\n ).Deno;\n if (!deno || typeof deno.readFileSync !== \"function\") {\n throw new HdiffError(\n \"PATCH_FAILED\",\n \"Deno runtime does not expose Deno.readFileSync\",\n );\n }\n\n try {\n return new WebAssembly.Module(deno.readFileSync(HDIFF_WASM_URL));\n } catch {\n throw new HdiffError(\n \"PATCH_FAILED\",\n `Failed to load hdiff.wasm for Deno runtime: ${HDIFF_WASM_URL.toString()}`,\n );\n }\n}\n\nexport { hdiff, HdiffError, type HdiffErrorCode };\n"],"mappings":";;;;;AAIA,MAAM,iBAAiB,IAAI,IAAI,wBAAwB,OAAO,KAAK,IAAI;AAEvE,uBAAuB,oBAAoB,CAAC;AAE5C,SAAS,qBAAyC;CAChD,MAAM,OACJ,WAGA;AACF,KAAI,CAAC,QAAQ,OAAO,KAAK,iBAAiB,WACxC,OAAM,IAAI,WACR,gBACA,iDACD;AAGH,KAAI;AACF,SAAO,IAAI,YAAY,OAAO,KAAK,aAAa,eAAe,CAAC;SAC1D;AACN,QAAM,IAAI,WACR,gBACA,+CAA+C,eAAe,UAAU,GACzE"}
@@ -0,0 +1,9 @@
1
+ //#region src/errors.d.ts
2
+ type HdiffErrorCode = "INVALID_HBC" | "NON_EXECUTION_FORM" | "BYTECODE_VERSION_MISMATCH" | "PATCH_FAILED";
3
+ declare class HdiffError extends Error {
4
+ readonly code: HdiffErrorCode;
5
+ constructor(code: HdiffErrorCode, message: string, options?: ErrorOptions);
6
+ }
7
+ //#endregion
8
+ export { HdiffError, HdiffErrorCode };
9
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";KAAY,cAAA;AAAA,cAMC,UAAA,SAAmB,KAAA;EAAA,SACd,IAAA,EAAM,cAAA;cAGpB,IAAA,EAAM,cAAA,EACN,OAAA,UACA,OAAA,GAAU,YAAA;AAAA"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ //#region src/errors.ts
2
+ var HdiffError = class extends Error {
3
+ code;
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.name = "HdiffError";
7
+ this.code = code;
8
+ }
9
+ };
10
+
11
+ //#endregion
12
+ export { HdiffError };
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["export type HdiffErrorCode =\n | \"INVALID_HBC\"\n | \"NON_EXECUTION_FORM\"\n | \"BYTECODE_VERSION_MISMATCH\"\n | \"PATCH_FAILED\";\n\nexport class HdiffError extends Error {\n public readonly code: HdiffErrorCode;\n\n public constructor(\n code: HdiffErrorCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = \"HdiffError\";\n this.code = code;\n }\n}\n"],"mappings":";AAMA,IAAa,aAAb,cAAgC,MAAM;CACpC,AAAgB;CAEhB,AAAO,YACL,MACA,SACA,SACA;AACA,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO;AACZ,OAAK,OAAO"}
@@ -0,0 +1,7 @@
1
+ import { BytesLike } from "./internal/bytes.js";
2
+
3
+ //#region src/hdiff.d.ts
4
+ declare function hdiff(base: BytesLike, next: BytesLike): Promise<Uint8Array>;
5
+ //#endregion
6
+ export { hdiff };
7
+ //# sourceMappingURL=hdiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hdiff.d.ts","names":[],"sources":["../src/hdiff.ts"],"mappings":";;;iBAKsB,KAAA,CACpB,IAAA,EAAM,SAAA,EACN,IAAA,EAAM,SAAA,GACL,OAAA,CAAQ,UAAA"}
package/dist/hdiff.js ADDED
@@ -0,0 +1,18 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { createBsdiffPatch } from "./internal/bsdiff.js";
3
+ import { toUint8Array } from "./internal/bytes.js";
4
+ import { validateExecutionHbc } from "./internal/hermes-validate.js";
5
+
6
+ //#region src/hdiff.ts
7
+ async function hdiff(base, next) {
8
+ const baseBytes = toUint8Array(base);
9
+ const nextBytes = toUint8Array(next);
10
+ const baseMeta = await validateExecutionHbc(baseBytes);
11
+ const nextMeta = await validateExecutionHbc(nextBytes);
12
+ if (baseMeta.version !== nextMeta.version) throw new HdiffError("BYTECODE_VERSION_MISMATCH", `HBC version mismatch: base=${baseMeta.version}, next=${nextMeta.version}`);
13
+ return await createBsdiffPatch(baseBytes, nextBytes);
14
+ }
15
+
16
+ //#endregion
17
+ export { hdiff };
18
+ //# sourceMappingURL=hdiff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hdiff.js","names":[],"sources":["../src/hdiff.ts"],"sourcesContent":["import { HdiffError } from \"./errors.js\";\nimport { createBsdiffPatch } from \"./internal/bsdiff.js\";\nimport { type BytesLike, toUint8Array } from \"./internal/bytes.js\";\nimport { validateExecutionHbc } from \"./internal/hermes-validate.js\";\n\nexport async function hdiff(\n base: BytesLike,\n next: BytesLike,\n): Promise<Uint8Array> {\n const baseBytes = toUint8Array(base);\n const nextBytes = toUint8Array(next);\n\n const baseMeta = await validateExecutionHbc(baseBytes);\n const nextMeta = await validateExecutionHbc(nextBytes);\n\n if (baseMeta.version !== nextMeta.version) {\n throw new HdiffError(\n \"BYTECODE_VERSION_MISMATCH\",\n `HBC version mismatch: base=${baseMeta.version}, next=${nextMeta.version}`,\n );\n }\n\n return await createBsdiffPatch(baseBytes, nextBytes);\n}\n"],"mappings":";;;;;;AAKA,eAAsB,MACpB,MACA,MACqB;CACrB,MAAM,YAAY,aAAa,KAAK;CACpC,MAAM,YAAY,aAAa,KAAK;CAEpC,MAAM,WAAW,MAAM,qBAAqB,UAAU;CACtD,MAAM,WAAW,MAAM,qBAAqB,UAAU;AAEtD,KAAI,SAAS,YAAY,SAAS,QAChC,OAAM,IAAI,WACR,6BACA,8BAA8B,SAAS,QAAQ,SAAS,SAAS,UAClE;AAGH,QAAO,MAAM,kBAAkB,WAAW,UAAU"}
@@ -0,0 +1,3 @@
1
+ import { HdiffError, HdiffErrorCode } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ export { HdiffError, type HdiffErrorCode, hdiff };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ import "./node.js";
4
+
5
+ export { HdiffError, hdiff };
@@ -0,0 +1,6 @@
1
+ //#region src/internal/bsdiff.d.ts
2
+ declare function createBsdiffPatch(base: Uint8Array, next: Uint8Array): Promise<Uint8Array>;
3
+ declare function applyBsdiffPatch(base: Uint8Array, patch: Uint8Array): Promise<Uint8Array>;
4
+ //#endregion
5
+ export { applyBsdiffPatch, createBsdiffPatch };
6
+ //# sourceMappingURL=bsdiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsdiff.d.ts","names":[],"sources":["../../src/internal/bsdiff.ts"],"mappings":";iBAgCsB,iBAAA,CACpB,IAAA,EAAM,UAAA,EACN,IAAA,EAAM,UAAA,GACL,OAAA,CAAQ,UAAA;AAAA,iBAkBW,gBAAA,CACpB,IAAA,EAAM,UAAA,EACN,KAAA,EAAO,UAAA,GACN,OAAA,CAAQ,UAAA"}
@@ -0,0 +1,103 @@
1
+ import { HdiffError } from "../errors.js";
2
+
3
+ //#region src/internal/bsdiff.ts
4
+ var BsdiffStatus = /* @__PURE__ */ function(BsdiffStatus) {
5
+ BsdiffStatus[BsdiffStatus["OK"] = 0] = "OK";
6
+ BsdiffStatus[BsdiffStatus["INVALID_INPUT"] = 1] = "INVALID_INPUT";
7
+ BsdiffStatus[BsdiffStatus["PATCH_FAILED"] = 2] = "PATCH_FAILED";
8
+ BsdiffStatus[BsdiffStatus["INVALID_PATCH"] = 3] = "INVALID_PATCH";
9
+ return BsdiffStatus;
10
+ }(BsdiffStatus || {});
11
+ let wasmPromise;
12
+ async function createBsdiffPatch(base, next) {
13
+ const wasm = await getBsdiffWasm();
14
+ const status = runBinaryOperation(wasm, base, next, (basePtr, baseLen, nextPtr, nextLen) => wasm.create_patch(basePtr, baseLen, nextPtr, nextLen));
15
+ if (status !== BsdiffStatus.OK) throw mapPatchError(status);
16
+ return readAndFreeOutput(wasm);
17
+ }
18
+ async function applyBsdiffPatch(base, patch) {
19
+ const wasm = await getBsdiffWasm();
20
+ const status = runBinaryOperation(wasm, base, patch, (basePtr, baseLen, patchPtr, patchLen) => wasm.apply_patch(basePtr, baseLen, patchPtr, patchLen));
21
+ if (status !== BsdiffStatus.OK) throw mapApplyError(status);
22
+ return readAndFreeOutput(wasm);
23
+ }
24
+ function runBinaryOperation(wasm, left, right, run) {
25
+ const leftPtr = wasm.alloc(left.byteLength);
26
+ const rightPtr = wasm.alloc(right.byteLength);
27
+ try {
28
+ writeBytesToMemory(wasm.memory, leftPtr, left);
29
+ writeBytesToMemory(wasm.memory, rightPtr, right);
30
+ return run(leftPtr, left.byteLength, rightPtr, right.byteLength);
31
+ } finally {
32
+ if (left.byteLength > 0) wasm.dealloc(leftPtr, left.byteLength);
33
+ if (right.byteLength > 0) wasm.dealloc(rightPtr, right.byteLength);
34
+ }
35
+ }
36
+ function writeBytesToMemory(memory, ptr, bytes) {
37
+ if (bytes.byteLength === 0) return;
38
+ new Uint8Array(memory.buffer, ptr, bytes.byteLength).set(bytes);
39
+ }
40
+ function readAndFreeOutput(wasm) {
41
+ const outPtr = wasm.output_ptr();
42
+ const outLen = wasm.output_len();
43
+ if (outLen === 0) {
44
+ wasm.free_output();
45
+ return new Uint8Array();
46
+ }
47
+ const output = new Uint8Array(wasm.memory.buffer, outPtr, outLen);
48
+ const copied = new Uint8Array(output);
49
+ wasm.free_output();
50
+ return copied;
51
+ }
52
+ function mapPatchError(status) {
53
+ if (status === BsdiffStatus.INVALID_INPUT) return new HdiffError("PATCH_FAILED", "Invalid input bytes provided to bsdiff wasm");
54
+ if (status === BsdiffStatus.PATCH_FAILED) return new HdiffError("PATCH_FAILED", "Failed to generate BSDIFF40 patch");
55
+ if (status === BsdiffStatus.INVALID_PATCH) return new HdiffError("PATCH_FAILED", "Unexpected patch-validation status while creating patch");
56
+ return new HdiffError("PATCH_FAILED", `Unknown bsdiff wasm status: ${status}`);
57
+ }
58
+ function mapApplyError(status) {
59
+ if (status === BsdiffStatus.INVALID_INPUT) return new HdiffError("PATCH_FAILED", "Invalid input bytes provided to bspatch wasm");
60
+ if (status === BsdiffStatus.INVALID_PATCH) return new HdiffError("PATCH_FAILED", "Invalid BSDIFF40 patch bytes");
61
+ if (status === BsdiffStatus.PATCH_FAILED) return new HdiffError("PATCH_FAILED", "Unexpected patch-generation status while applying patch");
62
+ return new HdiffError("PATCH_FAILED", `Unknown bspatch wasm status: ${status}`);
63
+ }
64
+ async function getBsdiffWasm() {
65
+ wasmPromise ??= loadBsdiffWasm();
66
+ return wasmPromise;
67
+ }
68
+ async function loadBsdiffWasm() {
69
+ const precompiled = getPrecompiledBsdiffModule();
70
+ const directExports = asBsdiffExports(precompiled);
71
+ if (directExports) return directExports;
72
+ if (isWasmModuleLike(precompiled)) return toBsdiffExports((await WebAssembly.instantiate(precompiled)).exports);
73
+ throw new HdiffError("PATCH_FAILED", "No precompiled bsdiff WASM configured. Import a runtime entry such as hermes-bundle-diff/node, /bun, /deno, or /worker.");
74
+ }
75
+ function getPrecompiledBsdiffModule() {
76
+ const hdiffGlobal = globalThis;
77
+ const mod = hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ ?? hdiffGlobal.__HDIFF_PRECOMPILED_WASM__;
78
+ if (mod && typeof mod === "object" && "default" in mod) return mod.default;
79
+ return mod;
80
+ }
81
+ function isWasmModuleLike(mod) {
82
+ if (mod instanceof WebAssembly.Module) return true;
83
+ return Object.prototype.toString.call(mod) === "[object WebAssembly.Module]";
84
+ }
85
+ function isWasmMemoryLike(memory) {
86
+ if (memory instanceof WebAssembly.Memory) return true;
87
+ return Object.prototype.toString.call(memory) === "[object WebAssembly.Memory]";
88
+ }
89
+ function toBsdiffExports(source) {
90
+ const direct = asBsdiffExports(source);
91
+ if (direct) return direct;
92
+ throw new HdiffError("PATCH_FAILED", "bsdiff wasm exports are incomplete");
93
+ }
94
+ function asBsdiffExports(source) {
95
+ if (source && typeof source === "object") {
96
+ const maybe = source;
97
+ if (isWasmMemoryLike(maybe.memory) && typeof maybe.alloc === "function" && typeof maybe.dealloc === "function" && typeof maybe.create_patch === "function" && typeof maybe.apply_patch === "function" && typeof maybe.output_ptr === "function" && typeof maybe.output_len === "function" && typeof maybe.free_output === "function") return maybe;
98
+ }
99
+ }
100
+
101
+ //#endregion
102
+ export { applyBsdiffPatch, createBsdiffPatch };
103
+ //# sourceMappingURL=bsdiff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsdiff.js","names":[],"sources":["../../src/internal/bsdiff.ts"],"sourcesContent":["import { HdiffError } from \"../errors.js\";\n\ntype BsdiffExports = {\n memory: WebAssembly.Memory;\n alloc: (len: number) => number;\n dealloc: (ptr: number, len: number) => void;\n create_patch: (\n basePtr: number,\n baseLen: number,\n nextPtr: number,\n nextLen: number,\n ) => number;\n apply_patch: (\n basePtr: number,\n baseLen: number,\n patchPtr: number,\n patchLen: number,\n ) => number;\n output_ptr: () => number;\n output_len: () => number;\n free_output: () => void;\n};\n\nenum BsdiffStatus {\n OK = 0,\n INVALID_INPUT = 1,\n PATCH_FAILED = 2,\n INVALID_PATCH = 3,\n}\n\nlet wasmPromise: Promise<BsdiffExports> | undefined;\n\nexport async function createBsdiffPatch(\n base: Uint8Array,\n next: Uint8Array,\n): Promise<Uint8Array> {\n const wasm = await getBsdiffWasm();\n\n const status = runBinaryOperation(\n wasm,\n base,\n next,\n (basePtr, baseLen, nextPtr, nextLen) =>\n wasm.create_patch(basePtr, baseLen, nextPtr, nextLen),\n );\n\n if (status !== BsdiffStatus.OK) {\n throw mapPatchError(status);\n }\n\n return readAndFreeOutput(wasm);\n}\n\nexport async function applyBsdiffPatch(\n base: Uint8Array,\n patch: Uint8Array,\n): Promise<Uint8Array> {\n const wasm = await getBsdiffWasm();\n\n const status = runBinaryOperation(\n wasm,\n base,\n patch,\n (basePtr, baseLen, patchPtr, patchLen) =>\n wasm.apply_patch(basePtr, baseLen, patchPtr, patchLen),\n );\n\n if (status !== BsdiffStatus.OK) {\n throw mapApplyError(status);\n }\n\n return readAndFreeOutput(wasm);\n}\n\nfunction runBinaryOperation(\n wasm: BsdiffExports,\n left: Uint8Array,\n right: Uint8Array,\n run: (\n leftPtr: number,\n leftLen: number,\n rightPtr: number,\n rightLen: number,\n ) => number,\n): number {\n const leftPtr = wasm.alloc(left.byteLength);\n const rightPtr = wasm.alloc(right.byteLength);\n\n try {\n writeBytesToMemory(wasm.memory, leftPtr, left);\n writeBytesToMemory(wasm.memory, rightPtr, right);\n return run(leftPtr, left.byteLength, rightPtr, right.byteLength);\n } finally {\n if (left.byteLength > 0) {\n wasm.dealloc(leftPtr, left.byteLength);\n }\n if (right.byteLength > 0) {\n wasm.dealloc(rightPtr, right.byteLength);\n }\n }\n}\n\nfunction writeBytesToMemory(\n memory: WebAssembly.Memory,\n ptr: number,\n bytes: Uint8Array,\n): void {\n if (bytes.byteLength === 0) {\n return;\n }\n new Uint8Array(memory.buffer, ptr, bytes.byteLength).set(bytes);\n}\n\nfunction readAndFreeOutput(wasm: BsdiffExports): Uint8Array {\n const outPtr = wasm.output_ptr();\n const outLen = wasm.output_len();\n\n if (outLen === 0) {\n wasm.free_output();\n return new Uint8Array();\n }\n\n const output = new Uint8Array(wasm.memory.buffer, outPtr, outLen);\n const copied = new Uint8Array(output);\n wasm.free_output();\n return copied;\n}\n\nfunction mapPatchError(status: number): HdiffError {\n if (status === BsdiffStatus.INVALID_INPUT) {\n return new HdiffError(\n \"PATCH_FAILED\",\n \"Invalid input bytes provided to bsdiff wasm\",\n );\n }\n if (status === BsdiffStatus.PATCH_FAILED) {\n return new HdiffError(\"PATCH_FAILED\", \"Failed to generate BSDIFF40 patch\");\n }\n if (status === BsdiffStatus.INVALID_PATCH) {\n return new HdiffError(\n \"PATCH_FAILED\",\n \"Unexpected patch-validation status while creating patch\",\n );\n }\n return new HdiffError(\n \"PATCH_FAILED\",\n `Unknown bsdiff wasm status: ${status}`,\n );\n}\n\nfunction mapApplyError(status: number): HdiffError {\n if (status === BsdiffStatus.INVALID_INPUT) {\n return new HdiffError(\n \"PATCH_FAILED\",\n \"Invalid input bytes provided to bspatch wasm\",\n );\n }\n if (status === BsdiffStatus.INVALID_PATCH) {\n return new HdiffError(\"PATCH_FAILED\", \"Invalid BSDIFF40 patch bytes\");\n }\n if (status === BsdiffStatus.PATCH_FAILED) {\n return new HdiffError(\n \"PATCH_FAILED\",\n \"Unexpected patch-generation status while applying patch\",\n );\n }\n return new HdiffError(\n \"PATCH_FAILED\",\n `Unknown bspatch wasm status: ${status}`,\n );\n}\n\nasync function getBsdiffWasm(): Promise<BsdiffExports> {\n wasmPromise ??= loadBsdiffWasm();\n return wasmPromise;\n}\n\nasync function loadBsdiffWasm(): Promise<BsdiffExports> {\n const precompiled = getPrecompiledBsdiffModule();\n const directExports = asBsdiffExports(precompiled);\n if (directExports) {\n return directExports;\n }\n\n if (isWasmModuleLike(precompiled)) {\n const instance = await WebAssembly.instantiate(precompiled);\n return toBsdiffExports(instance.exports);\n }\n\n throw new HdiffError(\n \"PATCH_FAILED\",\n \"No precompiled bsdiff WASM configured. Import a runtime entry such as hermes-bundle-diff/node, /bun, /deno, or /worker.\",\n );\n}\n\nfunction getPrecompiledBsdiffModule(): unknown {\n const hdiffGlobal = globalThis as {\n __HDIFF_PRECOMPILED_WASM__?: unknown;\n __HDIFF_PRECOMPILED_BSDIFF_WASM__?: unknown;\n };\n\n const mod =\n hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ ??\n hdiffGlobal.__HDIFF_PRECOMPILED_WASM__;\n if (mod && typeof mod === \"object\" && \"default\" in mod) {\n return (mod as { default?: unknown }).default;\n }\n return mod;\n}\n\nfunction isWasmModuleLike(mod: unknown): mod is WebAssembly.Module {\n if (mod instanceof WebAssembly.Module) {\n return true;\n }\n return Object.prototype.toString.call(mod) === \"[object WebAssembly.Module]\";\n}\n\nfunction isWasmMemoryLike(memory: unknown): memory is WebAssembly.Memory {\n if (memory instanceof WebAssembly.Memory) {\n return true;\n }\n return (\n Object.prototype.toString.call(memory) === \"[object WebAssembly.Memory]\"\n );\n}\n\nfunction toBsdiffExports(source: unknown): BsdiffExports {\n const direct = asBsdiffExports(source);\n if (direct) {\n return direct;\n }\n\n throw new HdiffError(\"PATCH_FAILED\", \"bsdiff wasm exports are incomplete\");\n}\n\nfunction asBsdiffExports(source: unknown): BsdiffExports | undefined {\n if (source && typeof source === \"object\") {\n const maybe = source as Partial<BsdiffExports>;\n if (\n isWasmMemoryLike(maybe.memory) &&\n typeof maybe.alloc === \"function\" &&\n typeof maybe.dealloc === \"function\" &&\n typeof maybe.create_patch === \"function\" &&\n typeof maybe.apply_patch === \"function\" &&\n typeof maybe.output_ptr === \"function\" &&\n typeof maybe.output_len === \"function\" &&\n typeof maybe.free_output === \"function\"\n ) {\n return maybe as BsdiffExports;\n }\n }\n return undefined;\n}\n"],"mappings":";;;AAuBA,IAAK,eAAL;AACE;AACA;AACA;AACA;;EAJG,mBAKJ;AAED,IAAI;AAEJ,eAAsB,kBACpB,MACA,MACqB;CACrB,MAAM,OAAO,MAAM,eAAe;CAElC,MAAM,SAAS,mBACb,MACA,MACA,OACC,SAAS,SAAS,SAAS,YAC1B,KAAK,aAAa,SAAS,SAAS,SAAS,QAAQ,CACxD;AAED,KAAI,WAAW,aAAa,GAC1B,OAAM,cAAc,OAAO;AAG7B,QAAO,kBAAkB,KAAK;;AAGhC,eAAsB,iBACpB,MACA,OACqB;CACrB,MAAM,OAAO,MAAM,eAAe;CAElC,MAAM,SAAS,mBACb,MACA,MACA,QACC,SAAS,SAAS,UAAU,aAC3B,KAAK,YAAY,SAAS,SAAS,UAAU,SAAS,CACzD;AAED,KAAI,WAAW,aAAa,GAC1B,OAAM,cAAc,OAAO;AAG7B,QAAO,kBAAkB,KAAK;;AAGhC,SAAS,mBACP,MACA,MACA,OACA,KAMQ;CACR,MAAM,UAAU,KAAK,MAAM,KAAK,WAAW;CAC3C,MAAM,WAAW,KAAK,MAAM,MAAM,WAAW;AAE7C,KAAI;AACF,qBAAmB,KAAK,QAAQ,SAAS,KAAK;AAC9C,qBAAmB,KAAK,QAAQ,UAAU,MAAM;AAChD,SAAO,IAAI,SAAS,KAAK,YAAY,UAAU,MAAM,WAAW;WACxD;AACR,MAAI,KAAK,aAAa,EACpB,MAAK,QAAQ,SAAS,KAAK,WAAW;AAExC,MAAI,MAAM,aAAa,EACrB,MAAK,QAAQ,UAAU,MAAM,WAAW;;;AAK9C,SAAS,mBACP,QACA,KACA,OACM;AACN,KAAI,MAAM,eAAe,EACvB;AAEF,KAAI,WAAW,OAAO,QAAQ,KAAK,MAAM,WAAW,CAAC,IAAI,MAAM;;AAGjE,SAAS,kBAAkB,MAAiC;CAC1D,MAAM,SAAS,KAAK,YAAY;CAChC,MAAM,SAAS,KAAK,YAAY;AAEhC,KAAI,WAAW,GAAG;AAChB,OAAK,aAAa;AAClB,SAAO,IAAI,YAAY;;CAGzB,MAAM,SAAS,IAAI,WAAW,KAAK,OAAO,QAAQ,QAAQ,OAAO;CACjE,MAAM,SAAS,IAAI,WAAW,OAAO;AACrC,MAAK,aAAa;AAClB,QAAO;;AAGT,SAAS,cAAc,QAA4B;AACjD,KAAI,WAAW,aAAa,cAC1B,QAAO,IAAI,WACT,gBACA,8CACD;AAEH,KAAI,WAAW,aAAa,aAC1B,QAAO,IAAI,WAAW,gBAAgB,oCAAoC;AAE5E,KAAI,WAAW,aAAa,cAC1B,QAAO,IAAI,WACT,gBACA,0DACD;AAEH,QAAO,IAAI,WACT,gBACA,+BAA+B,SAChC;;AAGH,SAAS,cAAc,QAA4B;AACjD,KAAI,WAAW,aAAa,cAC1B,QAAO,IAAI,WACT,gBACA,+CACD;AAEH,KAAI,WAAW,aAAa,cAC1B,QAAO,IAAI,WAAW,gBAAgB,+BAA+B;AAEvE,KAAI,WAAW,aAAa,aAC1B,QAAO,IAAI,WACT,gBACA,0DACD;AAEH,QAAO,IAAI,WACT,gBACA,gCAAgC,SACjC;;AAGH,eAAe,gBAAwC;AACrD,iBAAgB,gBAAgB;AAChC,QAAO;;AAGT,eAAe,iBAAyC;CACtD,MAAM,cAAc,4BAA4B;CAChD,MAAM,gBAAgB,gBAAgB,YAAY;AAClD,KAAI,cACF,QAAO;AAGT,KAAI,iBAAiB,YAAY,CAE/B,QAAO,iBADU,MAAM,YAAY,YAAY,YAAY,EAC3B,QAAQ;AAG1C,OAAM,IAAI,WACR,gBACA,0HACD;;AAGH,SAAS,6BAAsC;CAC7C,MAAM,cAAc;CAKpB,MAAM,MACJ,YAAY,qCACZ,YAAY;AACd,KAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,IACjD,QAAQ,IAA8B;AAExC,QAAO;;AAGT,SAAS,iBAAiB,KAAyC;AACjE,KAAI,eAAe,YAAY,OAC7B,QAAO;AAET,QAAO,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK;;AAGjD,SAAS,iBAAiB,QAA+C;AACvE,KAAI,kBAAkB,YAAY,OAChC,QAAO;AAET,QACE,OAAO,UAAU,SAAS,KAAK,OAAO,KAAK;;AAI/C,SAAS,gBAAgB,QAAgC;CACvD,MAAM,SAAS,gBAAgB,OAAO;AACtC,KAAI,OACF,QAAO;AAGT,OAAM,IAAI,WAAW,gBAAgB,qCAAqC;;AAG5E,SAAS,gBAAgB,QAA4C;AACnE,KAAI,UAAU,OAAO,WAAW,UAAU;EACxC,MAAM,QAAQ;AACd,MACE,iBAAiB,MAAM,OAAO,IAC9B,OAAO,MAAM,UAAU,cACvB,OAAO,MAAM,YAAY,cACzB,OAAO,MAAM,iBAAiB,cAC9B,OAAO,MAAM,gBAAgB,cAC7B,OAAO,MAAM,eAAe,cAC5B,OAAO,MAAM,eAAe,cAC5B,OAAO,MAAM,gBAAgB,WAE7B,QAAO"}
@@ -0,0 +1,8 @@
1
+ //#region src/internal/bytes.d.ts
2
+ type BytesLike = Uint8Array | ArrayBuffer;
3
+ declare function toUint8Array(value: BytesLike): Uint8Array;
4
+ declare function equalsBytes(a: Uint8Array, b: Uint8Array): boolean;
5
+ declare function cloneBytes(value: Uint8Array): Uint8Array;
6
+ //#endregion
7
+ export { BytesLike, cloneBytes, equalsBytes, toUint8Array };
8
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","names":[],"sources":["../../src/internal/bytes.ts"],"mappings":";KAAY,SAAA,GAAY,UAAA,GAAa,WAAA;AAAA,iBAErB,YAAA,CAAa,KAAA,EAAO,SAAA,GAAY,UAAA;AAAA,iBAUhC,WAAA,CAAY,CAAA,EAAG,UAAA,EAAY,CAAA,EAAG,UAAA;AAAA,iBAY9B,UAAA,CAAW,KAAA,EAAO,UAAA,GAAa,UAAA"}
@@ -0,0 +1,18 @@
1
+ //#region src/internal/bytes.ts
2
+ function toUint8Array(value) {
3
+ if (value instanceof Uint8Array) return value;
4
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
5
+ throw new TypeError("Expected Uint8Array or ArrayBuffer");
6
+ }
7
+ function equalsBytes(a, b) {
8
+ if (a.byteLength !== b.byteLength) return false;
9
+ for (let i = 0; i < a.byteLength; i += 1) if (a[i] !== b[i]) return false;
10
+ return true;
11
+ }
12
+ function cloneBytes(value) {
13
+ return new Uint8Array(value);
14
+ }
15
+
16
+ //#endregion
17
+ export { cloneBytes, equalsBytes, toUint8Array };
18
+ //# sourceMappingURL=bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.js","names":[],"sources":["../../src/internal/bytes.ts"],"sourcesContent":["export type BytesLike = Uint8Array | ArrayBuffer;\n\nexport function toUint8Array(value: BytesLike): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value);\n }\n throw new TypeError(\"Expected Uint8Array or ArrayBuffer\");\n}\n\nexport function equalsBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) {\n return false;\n }\n for (let i = 0; i < a.byteLength; i += 1) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\nexport function cloneBytes(value: Uint8Array): Uint8Array {\n return new Uint8Array(value);\n}\n"],"mappings":";AAEA,SAAgB,aAAa,OAA8B;AACzD,KAAI,iBAAiB,WACnB,QAAO;AAET,KAAI,iBAAiB,YACnB,QAAO,IAAI,WAAW,MAAM;AAE9B,OAAM,IAAI,UAAU,qCAAqC;;AAG3D,SAAgB,YAAY,GAAe,GAAwB;AACjE,KAAI,EAAE,eAAe,EAAE,WACrB,QAAO;AAET,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,YAAY,KAAK,EACrC,KAAI,EAAE,OAAO,EAAE,GACb,QAAO;AAGX,QAAO;;AAGT,SAAgB,WAAW,OAA+B;AACxD,QAAO,IAAI,WAAW,MAAM"}
@@ -0,0 +1,8 @@
1
+ //#region src/internal/hermes-validate.d.ts
2
+ type ValidatedHbc = {
3
+ version: number;
4
+ };
5
+ declare function validateExecutionHbc(input: Uint8Array): Promise<ValidatedHbc>;
6
+ //#endregion
7
+ export { validateExecutionHbc };
8
+ //# sourceMappingURL=hermes-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-validate.d.ts","names":[],"sources":["../../src/internal/hermes-validate.ts"],"mappings":";KAUK,YAAA;EACH,OAAA;AAAA;AAAA,iBAaoB,oBAAA,CACpB,KAAA,EAAO,UAAA,GACN,OAAA,CAAQ,YAAA"}
@@ -0,0 +1,84 @@
1
+ import { HdiffError } from "../errors.js";
2
+
3
+ //#region src/internal/hermes-validate.ts
4
+ var ValidateCode = /* @__PURE__ */ function(ValidateCode) {
5
+ ValidateCode[ValidateCode["OK"] = 0] = "OK";
6
+ ValidateCode[ValidateCode["TOO_SMALL"] = 1] = "TOO_SMALL";
7
+ ValidateCode[ValidateCode["INVALID_MAGIC"] = 2] = "INVALID_MAGIC";
8
+ ValidateCode[ValidateCode["DELTA_MAGIC"] = 3] = "DELTA_MAGIC";
9
+ ValidateCode[ValidateCode["INVALID_LENGTH"] = 4] = "INVALID_LENGTH";
10
+ return ValidateCode;
11
+ }(ValidateCode || {});
12
+ let wasmPromise;
13
+ async function validateExecutionHbc(input) {
14
+ const wasm = await getHermesValidator();
15
+ if (typeof wasm.alloc === "function" && typeof wasm.dealloc === "function") {
16
+ const ptr = wasm.alloc(input.byteLength);
17
+ try {
18
+ if (input.byteLength > 0) new Uint8Array(wasm.memory.buffer, ptr, input.byteLength).set(input);
19
+ const code = wasm.validate(ptr, input.byteLength);
20
+ if (code !== ValidateCode.OK) throw mapValidationError(code);
21
+ return { version: wasm.version(ptr) >>> 0 };
22
+ } finally {
23
+ if (input.byteLength > 0) wasm.dealloc(ptr, input.byteLength);
24
+ }
25
+ }
26
+ ensureMemorySize(wasm.memory, input.byteLength);
27
+ new Uint8Array(wasm.memory.buffer, 0, input.byteLength).set(input);
28
+ const code = wasm.validate(0, input.byteLength);
29
+ if (code !== ValidateCode.OK) throw mapValidationError(code);
30
+ return { version: wasm.version(0) >>> 0 };
31
+ }
32
+ async function getHermesValidator() {
33
+ wasmPromise ??= loadHermesValidator();
34
+ return wasmPromise;
35
+ }
36
+ async function loadHermesValidator() {
37
+ const precompiled = getPrecompiledHermesModule();
38
+ const directExports = asHermesExports(precompiled);
39
+ if (directExports) return directExports;
40
+ if (isWasmModuleLike(precompiled)) return toHermesExports((await WebAssembly.instantiate(precompiled)).exports);
41
+ throw new HdiffError("INVALID_HBC", "No precompiled Hermes validator WASM configured. Import a runtime entry such as hermes-bundle-diff/node, /bun, /deno, or /worker.");
42
+ }
43
+ function getPrecompiledHermesModule() {
44
+ const hdiffGlobal = globalThis;
45
+ const mod = hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ ?? hdiffGlobal.__HDIFF_PRECOMPILED_WASM__;
46
+ if (mod && typeof mod === "object" && "default" in mod) return mod.default;
47
+ return mod;
48
+ }
49
+ function ensureMemorySize(memory, requiredBytes) {
50
+ const pageSize = 64 * 1024;
51
+ const currentBytes = memory.buffer.byteLength;
52
+ if (currentBytes >= requiredBytes) return;
53
+ const bytesNeeded = requiredBytes - currentBytes;
54
+ const additionalPages = Math.ceil(bytesNeeded / pageSize);
55
+ memory.grow(additionalPages);
56
+ }
57
+ function mapValidationError(code) {
58
+ if (code === ValidateCode.DELTA_MAGIC) return new HdiffError("NON_EXECUTION_FORM", "Input HBC is delta-form. execution-form HBC is required.");
59
+ if (code === ValidateCode.TOO_SMALL || code === ValidateCode.INVALID_MAGIC || code === ValidateCode.INVALID_LENGTH) return new HdiffError("INVALID_HBC", "Input is not a valid execution-form HBC");
60
+ return new HdiffError("INVALID_HBC", `Unknown Hermes validation error: ${code}`);
61
+ }
62
+ function isWasmModuleLike(mod) {
63
+ if (mod instanceof WebAssembly.Module) return true;
64
+ return Object.prototype.toString.call(mod) === "[object WebAssembly.Module]";
65
+ }
66
+ function isWasmMemoryLike(memory) {
67
+ if (memory instanceof WebAssembly.Memory) return true;
68
+ return Object.prototype.toString.call(memory) === "[object WebAssembly.Memory]";
69
+ }
70
+ function toHermesExports(source) {
71
+ const direct = asHermesExports(source);
72
+ if (direct) return direct;
73
+ throw new HdiffError("INVALID_HBC", "Hermes validator WASM exports are incomplete");
74
+ }
75
+ function asHermesExports(source) {
76
+ if (source && typeof source === "object") {
77
+ const maybe = source;
78
+ if (isWasmMemoryLike(maybe.memory) && typeof maybe.validate === "function" && typeof maybe.version === "function") return maybe;
79
+ }
80
+ }
81
+
82
+ //#endregion
83
+ export { validateExecutionHbc };
84
+ //# sourceMappingURL=hermes-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-validate.js","names":[],"sources":["../../src/internal/hermes-validate.ts"],"sourcesContent":["import { HdiffError } from \"../errors.js\";\n\ntype HermesValidateExports = {\n memory: WebAssembly.Memory;\n validate: (ptr: number, len: number) => number;\n version: (ptr: number) => number;\n alloc?: (len: number) => number;\n dealloc?: (ptr: number, len: number) => void;\n};\n\ntype ValidatedHbc = {\n version: number;\n};\n\nenum ValidateCode {\n OK = 0,\n TOO_SMALL = 1,\n INVALID_MAGIC = 2,\n DELTA_MAGIC = 3,\n INVALID_LENGTH = 4,\n}\n\nlet wasmPromise: Promise<HermesValidateExports> | undefined;\n\nexport async function validateExecutionHbc(\n input: Uint8Array,\n): Promise<ValidatedHbc> {\n const wasm = await getHermesValidator();\n if (typeof wasm.alloc === \"function\" && typeof wasm.dealloc === \"function\") {\n const ptr = wasm.alloc(input.byteLength);\n try {\n if (input.byteLength > 0) {\n new Uint8Array(wasm.memory.buffer, ptr, input.byteLength).set(input);\n }\n\n const code = wasm.validate(ptr, input.byteLength);\n if (code !== ValidateCode.OK) {\n throw mapValidationError(code);\n }\n\n return { version: wasm.version(ptr) >>> 0 };\n } finally {\n if (input.byteLength > 0) {\n wasm.dealloc(ptr, input.byteLength);\n }\n }\n }\n\n ensureMemorySize(wasm.memory, input.byteLength);\n const view = new Uint8Array(wasm.memory.buffer, 0, input.byteLength);\n view.set(input);\n\n const code = wasm.validate(0, input.byteLength);\n if (code !== ValidateCode.OK) {\n throw mapValidationError(code);\n }\n\n return { version: wasm.version(0) >>> 0 };\n}\n\nasync function getHermesValidator(): Promise<HermesValidateExports> {\n wasmPromise ??= loadHermesValidator();\n return wasmPromise;\n}\n\nasync function loadHermesValidator(): Promise<HermesValidateExports> {\n const precompiled = getPrecompiledHermesModule();\n const directExports = asHermesExports(precompiled);\n if (directExports) {\n return directExports;\n }\n\n if (isWasmModuleLike(precompiled)) {\n const instance = await WebAssembly.instantiate(precompiled);\n return toHermesExports(instance.exports);\n }\n\n throw new HdiffError(\n \"INVALID_HBC\",\n \"No precompiled Hermes validator WASM configured. Import a runtime entry such as hermes-bundle-diff/node, /bun, /deno, or /worker.\",\n );\n}\n\nfunction getPrecompiledHermesModule(): unknown {\n const hdiffGlobal = globalThis as {\n __HDIFF_PRECOMPILED_WASM__?: unknown;\n __HDIFF_PRECOMPILED_HERMES_HBC_WASM__?: unknown;\n };\n\n const mod =\n hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ ??\n hdiffGlobal.__HDIFF_PRECOMPILED_WASM__;\n if (mod && typeof mod === \"object\" && \"default\" in mod) {\n return (mod as { default?: unknown }).default;\n }\n return mod;\n}\n\nfunction ensureMemorySize(\n memory: WebAssembly.Memory,\n requiredBytes: number,\n): void {\n const pageSize = 64 * 1024;\n const currentBytes = memory.buffer.byteLength;\n if (currentBytes >= requiredBytes) {\n return;\n }\n\n const bytesNeeded = requiredBytes - currentBytes;\n const additionalPages = Math.ceil(bytesNeeded / pageSize);\n memory.grow(additionalPages);\n}\n\nfunction mapValidationError(code: number): HdiffError {\n if (code === ValidateCode.DELTA_MAGIC) {\n return new HdiffError(\n \"NON_EXECUTION_FORM\",\n \"Input HBC is delta-form. execution-form HBC is required.\",\n );\n }\n if (\n code === ValidateCode.TOO_SMALL ||\n code === ValidateCode.INVALID_MAGIC ||\n code === ValidateCode.INVALID_LENGTH\n ) {\n return new HdiffError(\n \"INVALID_HBC\",\n \"Input is not a valid execution-form HBC\",\n );\n }\n return new HdiffError(\n \"INVALID_HBC\",\n `Unknown Hermes validation error: ${code}`,\n );\n}\n\nfunction isWasmModuleLike(mod: unknown): mod is WebAssembly.Module {\n if (mod instanceof WebAssembly.Module) {\n return true;\n }\n return Object.prototype.toString.call(mod) === \"[object WebAssembly.Module]\";\n}\n\nfunction isWasmMemoryLike(memory: unknown): memory is WebAssembly.Memory {\n if (memory instanceof WebAssembly.Memory) {\n return true;\n }\n return (\n Object.prototype.toString.call(memory) === \"[object WebAssembly.Memory]\"\n );\n}\n\nfunction toHermesExports(source: unknown): HermesValidateExports {\n const direct = asHermesExports(source);\n if (direct) {\n return direct;\n }\n\n throw new HdiffError(\n \"INVALID_HBC\",\n \"Hermes validator WASM exports are incomplete\",\n );\n}\n\nfunction asHermesExports(source: unknown): HermesValidateExports | undefined {\n if (source && typeof source === \"object\") {\n const maybe = source as Partial<HermesValidateExports>;\n if (\n isWasmMemoryLike(maybe.memory) &&\n typeof maybe.validate === \"function\" &&\n typeof maybe.version === \"function\"\n ) {\n return maybe as HermesValidateExports;\n }\n }\n return undefined;\n}\n"],"mappings":";;;AAcA,IAAK,eAAL;AACE;AACA;AACA;AACA;AACA;;EALG,mBAMJ;AAED,IAAI;AAEJ,eAAsB,qBACpB,OACuB;CACvB,MAAM,OAAO,MAAM,oBAAoB;AACvC,KAAI,OAAO,KAAK,UAAU,cAAc,OAAO,KAAK,YAAY,YAAY;EAC1E,MAAM,MAAM,KAAK,MAAM,MAAM,WAAW;AACxC,MAAI;AACF,OAAI,MAAM,aAAa,EACrB,KAAI,WAAW,KAAK,OAAO,QAAQ,KAAK,MAAM,WAAW,CAAC,IAAI,MAAM;GAGtE,MAAM,OAAO,KAAK,SAAS,KAAK,MAAM,WAAW;AACjD,OAAI,SAAS,aAAa,GACxB,OAAM,mBAAmB,KAAK;AAGhC,UAAO,EAAE,SAAS,KAAK,QAAQ,IAAI,KAAK,GAAG;YACnC;AACR,OAAI,MAAM,aAAa,EACrB,MAAK,QAAQ,KAAK,MAAM,WAAW;;;AAKzC,kBAAiB,KAAK,QAAQ,MAAM,WAAW;AAE/C,CADa,IAAI,WAAW,KAAK,OAAO,QAAQ,GAAG,MAAM,WAAW,CAC/D,IAAI,MAAM;CAEf,MAAM,OAAO,KAAK,SAAS,GAAG,MAAM,WAAW;AAC/C,KAAI,SAAS,aAAa,GACxB,OAAM,mBAAmB,KAAK;AAGhC,QAAO,EAAE,SAAS,KAAK,QAAQ,EAAE,KAAK,GAAG;;AAG3C,eAAe,qBAAqD;AAClE,iBAAgB,qBAAqB;AACrC,QAAO;;AAGT,eAAe,sBAAsD;CACnE,MAAM,cAAc,4BAA4B;CAChD,MAAM,gBAAgB,gBAAgB,YAAY;AAClD,KAAI,cACF,QAAO;AAGT,KAAI,iBAAiB,YAAY,CAE/B,QAAO,iBADU,MAAM,YAAY,YAAY,YAAY,EAC3B,QAAQ;AAG1C,OAAM,IAAI,WACR,eACA,oIACD;;AAGH,SAAS,6BAAsC;CAC7C,MAAM,cAAc;CAKpB,MAAM,MACJ,YAAY,yCACZ,YAAY;AACd,KAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,IACjD,QAAQ,IAA8B;AAExC,QAAO;;AAGT,SAAS,iBACP,QACA,eACM;CACN,MAAM,WAAW,KAAK;CACtB,MAAM,eAAe,OAAO,OAAO;AACnC,KAAI,gBAAgB,cAClB;CAGF,MAAM,cAAc,gBAAgB;CACpC,MAAM,kBAAkB,KAAK,KAAK,cAAc,SAAS;AACzD,QAAO,KAAK,gBAAgB;;AAG9B,SAAS,mBAAmB,MAA0B;AACpD,KAAI,SAAS,aAAa,YACxB,QAAO,IAAI,WACT,sBACA,2DACD;AAEH,KACE,SAAS,aAAa,aACtB,SAAS,aAAa,iBACtB,SAAS,aAAa,eAEtB,QAAO,IAAI,WACT,eACA,0CACD;AAEH,QAAO,IAAI,WACT,eACA,oCAAoC,OACrC;;AAGH,SAAS,iBAAiB,KAAyC;AACjE,KAAI,eAAe,YAAY,OAC7B,QAAO;AAET,QAAO,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK;;AAGjD,SAAS,iBAAiB,QAA+C;AACvE,KAAI,kBAAkB,YAAY,OAChC,QAAO;AAET,QACE,OAAO,UAAU,SAAS,KAAK,OAAO,KAAK;;AAI/C,SAAS,gBAAgB,QAAwC;CAC/D,MAAM,SAAS,gBAAgB,OAAO;AACtC,KAAI,OACF,QAAO;AAGT,OAAM,IAAI,WACR,eACA,+CACD;;AAGH,SAAS,gBAAgB,QAAoD;AAC3E,KAAI,UAAU,OAAO,WAAW,UAAU;EACxC,MAAM,QAAQ;AACd,MACE,iBAAiB,MAAM,OAAO,IAC9B,OAAO,MAAM,aAAa,cAC1B,OAAO,MAAM,YAAY,WAEzB,QAAO"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { HdiffError, HdiffErrorCode } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ export { HdiffError, type HdiffErrorCode, hdiff };
package/dist/node.js ADDED
@@ -0,0 +1,19 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ import { installPrecompiledWasm } from "./precompiled.js";
4
+ import { readFileSync } from "node:fs";
5
+
6
+ //#region src/node.ts
7
+ const HDIFF_WASM_URL = new URL("../assets/hdiff.wasm", import.meta.url);
8
+ installPrecompiledWasm(loadNodeWasmModule());
9
+ function loadNodeWasmModule() {
10
+ try {
11
+ return new WebAssembly.Module(readFileSync(HDIFF_WASM_URL));
12
+ } catch {
13
+ throw new HdiffError("PATCH_FAILED", `Failed to load hdiff.wasm for Node runtime: ${HDIFF_WASM_URL.toString()}`);
14
+ }
15
+ }
16
+
17
+ //#endregion
18
+ export { HdiffError, hdiff };
19
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","names":[],"sources":["../src/node.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { HdiffError, type HdiffErrorCode } from \"./errors.js\";\nimport { hdiff } from \"./hdiff.js\";\nimport { installPrecompiledWasm } from \"./precompiled.js\";\n\nconst HDIFF_WASM_URL = new URL(\"../assets/hdiff.wasm\", import.meta.url);\n\ninstallPrecompiledWasm(loadNodeWasmModule());\n\nfunction loadNodeWasmModule(): WebAssembly.Module {\n try {\n return new WebAssembly.Module(readFileSync(HDIFF_WASM_URL));\n } catch {\n throw new HdiffError(\n \"PATCH_FAILED\",\n `Failed to load hdiff.wasm for Node runtime: ${HDIFF_WASM_URL.toString()}`,\n );\n }\n}\n\nexport { hdiff, HdiffError, type HdiffErrorCode };\n"],"mappings":";;;;;;AAKA,MAAM,iBAAiB,IAAI,IAAI,wBAAwB,OAAO,KAAK,IAAI;AAEvE,uBAAuB,oBAAoB,CAAC;AAE5C,SAAS,qBAAyC;AAChD,KAAI;AACF,SAAO,IAAI,YAAY,OAAO,aAAa,eAAe,CAAC;SACrD;AACN,QAAM,IAAI,WACR,gBACA,+CAA+C,eAAe,UAAU,GACzE"}
@@ -0,0 +1,5 @@
1
+ //#region src/precompiled.d.ts
2
+ declare function installPrecompiledWasm(wasmModule: WebAssembly.Module): void;
3
+ //#endregion
4
+ export { installPrecompiledWasm };
5
+ //# sourceMappingURL=precompiled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precompiled.d.ts","names":[],"sources":["../src/precompiled.ts"],"mappings":";iBAMgB,sBAAA,CAAuB,UAAA,EAAY,WAAA,CAAY,MAAA"}
@@ -0,0 +1,11 @@
1
+ //#region src/precompiled.ts
2
+ function installPrecompiledWasm(wasmModule) {
3
+ const hdiffGlobal = globalThis;
4
+ if (hdiffGlobal.__HDIFF_PRECOMPILED_WASM__ === void 0) hdiffGlobal.__HDIFF_PRECOMPILED_WASM__ = wasmModule;
5
+ if (hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ === void 0) hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ = wasmModule;
6
+ if (hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ === void 0) hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ = wasmModule;
7
+ }
8
+
9
+ //#endregion
10
+ export { installPrecompiledWasm };
11
+ //# sourceMappingURL=precompiled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precompiled.js","names":[],"sources":["../src/precompiled.ts"],"sourcesContent":["type HdiffGlobal = {\n __HDIFF_PRECOMPILED_WASM__?: unknown;\n __HDIFF_PRECOMPILED_BSDIFF_WASM__?: unknown;\n __HDIFF_PRECOMPILED_HERMES_HBC_WASM__?: unknown;\n};\n\nexport function installPrecompiledWasm(wasmModule: WebAssembly.Module): void {\n const hdiffGlobal = globalThis as HdiffGlobal;\n\n if (hdiffGlobal.__HDIFF_PRECOMPILED_WASM__ === undefined) {\n hdiffGlobal.__HDIFF_PRECOMPILED_WASM__ = wasmModule;\n }\n if (hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ === undefined) {\n hdiffGlobal.__HDIFF_PRECOMPILED_BSDIFF_WASM__ = wasmModule;\n }\n if (hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ === undefined) {\n hdiffGlobal.__HDIFF_PRECOMPILED_HERMES_HBC_WASM__ = wasmModule;\n }\n}\n"],"mappings":";AAMA,SAAgB,uBAAuB,YAAsC;CAC3E,MAAM,cAAc;AAEpB,KAAI,YAAY,+BAA+B,OAC7C,aAAY,6BAA6B;AAE3C,KAAI,YAAY,sCAAsC,OACpD,aAAY,oCAAoC;AAElD,KAAI,YAAY,0CAA0C,OACxD,aAAY,wCAAwC"}
@@ -0,0 +1,3 @@
1
+ import { HdiffError, HdiffErrorCode } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ export { HdiffError, type HdiffErrorCode, hdiff };
package/dist/worker.js ADDED
@@ -0,0 +1,11 @@
1
+ import { HdiffError } from "./errors.js";
2
+ import { hdiff } from "./hdiff.js";
3
+ import { installPrecompiledWasm } from "./precompiled.js";
4
+ import hdiffWasmModule from "../assets/hdiff.wasm";
5
+
6
+ //#region src/worker.ts
7
+ installPrecompiledWasm(hdiffWasmModule);
8
+
9
+ //#endregion
10
+ export { HdiffError, hdiff };
11
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","names":[],"sources":["../src/worker.ts"],"sourcesContent":["import hdiffWasmModule from \"../assets/hdiff.wasm\";\nimport { HdiffError, type HdiffErrorCode } from \"./errors.js\";\nimport { hdiff } from \"./hdiff.js\";\nimport { installPrecompiledWasm } from \"./precompiled.js\";\n\ninstallPrecompiledWasm(hdiffWasmModule);\n\nexport { hdiff, HdiffError, type HdiffErrorCode };\n"],"mappings":";;;;;;AAKA,uBAAuB,gBAAgB"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@hot-updater/bsdiff",
3
+ "version": "0.25.13",
4
+ "description": "Hot Updater BSdiff library that produces BSDIFF40 patches for RN bspatch flows",
5
+ "type": "module",
6
+ "main": "./dist/node.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "workerd": "./dist/worker.js",
12
+ "worker": "./dist/worker.js",
13
+ "deno": "./dist/deno.js",
14
+ "bun": "./dist/bun.js",
15
+ "node": "./dist/node.js",
16
+ "import": "./dist/node.js",
17
+ "default": "./dist/node.js"
18
+ },
19
+ "./node": {
20
+ "types": "./dist/node.d.ts",
21
+ "import": "./dist/node.js",
22
+ "default": "./dist/node.js"
23
+ },
24
+ "./deno": {
25
+ "types": "./dist/deno.d.ts",
26
+ "import": "./dist/deno.js",
27
+ "default": "./dist/deno.js"
28
+ },
29
+ "./bun": {
30
+ "types": "./dist/bun.d.ts",
31
+ "import": "./dist/bun.js",
32
+ "default": "./dist/bun.js"
33
+ },
34
+ "./worker": {
35
+ "types": "./dist/worker.d.ts",
36
+ "import": "./dist/worker.js",
37
+ "default": "./dist/worker.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "assets",
43
+ "scripts/wasm-build",
44
+ "rust/hdiff-wasm/Cargo.toml",
45
+ "rust/hdiff-wasm/src"
46
+ ],
47
+ "keywords": [
48
+ "hermes",
49
+ "hbc",
50
+ "bsdiff",
51
+ "bspatch",
52
+ "react-native"
53
+ ],
54
+ "devDependencies": {
55
+ "@types/node": "^25.3.2",
56
+ "tsdown": "0.21.0-beta.2",
57
+ "typescript": "^5.9.3",
58
+ "vitest": "^4.0.18",
59
+ "wrangler": "^4.69.0"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "scripts": {
65
+ "build": "pnpm run build:wasm && tsdown",
66
+ "build:watch": "tsdown --watch",
67
+ "build:wasm": "node ./scripts/wasm-build/build-wasm.mjs",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest",
70
+ "test:runtime:node": "vitest run tests/runtime.node.test.ts",
71
+ "test:runtime:endpoints": "vitest run tests/runtime.endpoints.test.ts"
72
+ }
73
+ }
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "hdiff_wasm"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ publish = false
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ bsdiff = "0.2.1"
12
+ bzip2 = "0.6.1"
13
+
14
+ [profile.release]
15
+ opt-level = "z"
16
+ lto = true
17
+ codegen-units = 1
18
+ panic = "abort"
@@ -0,0 +1,380 @@
1
+ use bzip2::read::BzDecoder;
2
+ use bzip2::write::BzEncoder;
3
+ use bzip2::Compression;
4
+ use std::io::Cursor;
5
+ use std::io::Read;
6
+ use std::io::Write;
7
+
8
+ const MIN_HEADER_LEN: u32 = 128;
9
+ const EXEC_MAGIC_LO: u32 = 0x03BC_1FC6;
10
+ const EXEC_MAGIC_HI: u32 = 0x1F19_03C1;
11
+ const DELTA_MAGIC_LO: u32 = 0xFC43_E039;
12
+ const DELTA_MAGIC_HI: u32 = 0xE0E6_FC3E;
13
+
14
+ const OFFSET_VERSION: u32 = 8;
15
+ const OFFSET_FILE_LENGTH: u32 = 32;
16
+ const OFFSET_MAGIC_HI: u32 = 4;
17
+
18
+ const VALIDATE_OK: u32 = 0;
19
+ const VALIDATE_TOO_SMALL: u32 = 1;
20
+ const VALIDATE_INVALID_MAGIC: u32 = 2;
21
+ const VALIDATE_DELTA_MAGIC: u32 = 3;
22
+ const VALIDATE_INVALID_LENGTH: u32 = 4;
23
+
24
+ const STATUS_OK: u32 = 0;
25
+ const STATUS_INVALID_INPUT: u32 = 1;
26
+ const STATUS_PATCH_FAILED: u32 = 2;
27
+ const STATUS_INVALID_PATCH: u32 = 3;
28
+
29
+ static mut LAST_OUTPUT_PTR: *mut u8 = std::ptr::null_mut();
30
+ static mut LAST_OUTPUT_LEN: usize = 0;
31
+ static mut LAST_OUTPUT_CAP: usize = 0;
32
+
33
+ #[no_mangle]
34
+ pub unsafe extern "C" fn validate(ptr: u32, len: u32) -> u32 {
35
+ if len < MIN_HEADER_LEN {
36
+ return VALIDATE_TOO_SMALL;
37
+ }
38
+
39
+ let magic_lo = read_u32(ptr);
40
+ let magic_hi = read_u32(ptr.wrapping_add(OFFSET_MAGIC_HI));
41
+
42
+ if magic_lo == EXEC_MAGIC_LO {
43
+ if magic_hi != EXEC_MAGIC_HI {
44
+ return VALIDATE_INVALID_MAGIC;
45
+ }
46
+ } else {
47
+ if magic_lo == DELTA_MAGIC_LO && magic_hi == DELTA_MAGIC_HI {
48
+ return VALIDATE_DELTA_MAGIC;
49
+ }
50
+ return VALIDATE_INVALID_MAGIC;
51
+ }
52
+
53
+ let file_length = read_u32(ptr.wrapping_add(OFFSET_FILE_LENGTH));
54
+ if file_length != len {
55
+ return VALIDATE_INVALID_LENGTH;
56
+ }
57
+
58
+ VALIDATE_OK
59
+ }
60
+
61
+ #[no_mangle]
62
+ pub unsafe extern "C" fn version(ptr: u32) -> u32 {
63
+ read_u32(ptr.wrapping_add(OFFSET_VERSION))
64
+ }
65
+
66
+ #[no_mangle]
67
+ pub extern "C" fn alloc(len: u32) -> u32 {
68
+ let mut bytes = Vec::<u8>::with_capacity(len as usize);
69
+ let ptr = bytes.as_mut_ptr();
70
+ std::mem::forget(bytes);
71
+ ptr as u32
72
+ }
73
+
74
+ #[no_mangle]
75
+ pub unsafe extern "C" fn dealloc(ptr: u32, len: u32) {
76
+ if ptr == 0 || len == 0 {
77
+ return;
78
+ }
79
+
80
+ drop(Vec::from_raw_parts(
81
+ ptr as *mut u8,
82
+ len as usize,
83
+ len as usize,
84
+ ));
85
+ }
86
+
87
+ #[no_mangle]
88
+ pub unsafe extern "C" fn create_patch(
89
+ base_ptr: u32,
90
+ base_len: u32,
91
+ next_ptr: u32,
92
+ next_len: u32,
93
+ ) -> u32 {
94
+ let base = match copy_input(base_ptr, base_len) {
95
+ Ok(base) => base,
96
+ Err(_) => return STATUS_INVALID_INPUT,
97
+ };
98
+ let next = match copy_input(next_ptr, next_len) {
99
+ Ok(next) => next,
100
+ Err(_) => return STATUS_INVALID_INPUT,
101
+ };
102
+
103
+ match generate_bsdiff40_patch(&base, &next) {
104
+ Ok(patch) => {
105
+ store_output(patch);
106
+ STATUS_OK
107
+ }
108
+ Err(_) => STATUS_PATCH_FAILED,
109
+ }
110
+ }
111
+
112
+ #[no_mangle]
113
+ pub unsafe extern "C" fn apply_patch(
114
+ base_ptr: u32,
115
+ base_len: u32,
116
+ patch_ptr: u32,
117
+ patch_len: u32,
118
+ ) -> u32 {
119
+ let base = match copy_input(base_ptr, base_len) {
120
+ Ok(base) => base,
121
+ Err(_) => return STATUS_INVALID_INPUT,
122
+ };
123
+ let patch = match copy_input(patch_ptr, patch_len) {
124
+ Ok(patch) => patch,
125
+ Err(_) => return STATUS_INVALID_INPUT,
126
+ };
127
+
128
+ match apply_bsdiff40_patch(&base, &patch) {
129
+ Ok(next) => {
130
+ store_output(next);
131
+ STATUS_OK
132
+ }
133
+ Err(_) => STATUS_INVALID_PATCH,
134
+ }
135
+ }
136
+
137
+ #[no_mangle]
138
+ pub extern "C" fn output_ptr() -> u32 {
139
+ unsafe { LAST_OUTPUT_PTR as u32 }
140
+ }
141
+
142
+ #[no_mangle]
143
+ pub extern "C" fn output_len() -> u32 {
144
+ unsafe { LAST_OUTPUT_LEN as u32 }
145
+ }
146
+
147
+ #[no_mangle]
148
+ pub extern "C" fn free_output() {
149
+ unsafe {
150
+ free_output_buffer();
151
+ }
152
+ }
153
+
154
+ #[inline]
155
+ unsafe fn read_u32(ptr: u32) -> u32 {
156
+ std::ptr::read_unaligned(ptr as *const u32)
157
+ }
158
+
159
+ unsafe fn copy_input(ptr: u32, len: u32) -> Result<Vec<u8>, ()> {
160
+ if len == 0 {
161
+ return Ok(Vec::new());
162
+ }
163
+ if ptr == 0 {
164
+ return Err(());
165
+ }
166
+
167
+ let bytes = std::slice::from_raw_parts(ptr as *const u8, len as usize);
168
+ Ok(bytes.to_vec())
169
+ }
170
+
171
+ fn store_output(bytes: Vec<u8>) {
172
+ unsafe {
173
+ free_output_buffer();
174
+
175
+ let mut output = bytes;
176
+ LAST_OUTPUT_PTR = output.as_mut_ptr();
177
+ LAST_OUTPUT_LEN = output.len();
178
+ LAST_OUTPUT_CAP = output.capacity();
179
+ std::mem::forget(output);
180
+ }
181
+ }
182
+
183
+ unsafe fn free_output_buffer() {
184
+ if LAST_OUTPUT_PTR.is_null() {
185
+ LAST_OUTPUT_LEN = 0;
186
+ LAST_OUTPUT_CAP = 0;
187
+ return;
188
+ }
189
+
190
+ drop(Vec::from_raw_parts(
191
+ LAST_OUTPUT_PTR,
192
+ LAST_OUTPUT_LEN,
193
+ LAST_OUTPUT_CAP,
194
+ ));
195
+ LAST_OUTPUT_PTR = std::ptr::null_mut();
196
+ LAST_OUTPUT_LEN = 0;
197
+ LAST_OUTPUT_CAP = 0;
198
+ }
199
+
200
+ fn generate_bsdiff40_patch(old: &[u8], new: &[u8]) -> Result<Vec<u8>, String> {
201
+ let mut legacy_patch = Vec::new();
202
+ bsdiff::diff(old, new, &mut legacy_patch).map_err(|error| format!("{error}"))?;
203
+
204
+ let (ctrl_block, diff_block, extra_block) = split_legacy_patch(&legacy_patch)?;
205
+
206
+ let ctrl_bz = bzip2_compress(&ctrl_block)?;
207
+ let diff_bz = bzip2_compress(&diff_block)?;
208
+ let extra_bz = bzip2_compress(&extra_block)?;
209
+
210
+ let mut patch = Vec::with_capacity(32 + ctrl_bz.len() + diff_bz.len() + extra_bz.len());
211
+ patch.extend_from_slice(b"BSDIFF40");
212
+ write_offt(ctrl_bz.len() as i64, &mut patch)?;
213
+ write_offt(diff_bz.len() as i64, &mut patch)?;
214
+ write_offt(new.len() as i64, &mut patch)?;
215
+ patch.extend_from_slice(&ctrl_bz);
216
+ patch.extend_from_slice(&diff_bz);
217
+ patch.extend_from_slice(&extra_bz);
218
+
219
+ Ok(patch)
220
+ }
221
+
222
+ fn split_legacy_patch(legacy_patch: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), String> {
223
+ let mut controls = Vec::new();
224
+ let mut diff_data = Vec::new();
225
+ let mut extra_data = Vec::new();
226
+
227
+ let mut cursor = 0usize;
228
+ while cursor < legacy_patch.len() {
229
+ if legacy_patch.len() - cursor < 24 {
230
+ return Err("invalid legacy patch control length".to_string());
231
+ }
232
+
233
+ let ctrl_bytes = &legacy_patch[cursor..cursor + 24];
234
+ let add_len = read_offt(&ctrl_bytes[0..8])?;
235
+ let copy_len = read_offt(&ctrl_bytes[8..16])?;
236
+ let _seek = read_offt(&ctrl_bytes[16..24])?;
237
+
238
+ if add_len < 0 || copy_len < 0 {
239
+ return Err("invalid legacy patch control values".to_string());
240
+ }
241
+
242
+ let add_len = usize::try_from(add_len).map_err(|_| "add length overflow".to_string())?;
243
+ let copy_len =
244
+ usize::try_from(copy_len).map_err(|_| "copy length overflow".to_string())?;
245
+ cursor += 24;
246
+
247
+ if legacy_patch.len() - cursor < add_len {
248
+ return Err("legacy patch truncated in diff section".to_string());
249
+ }
250
+ diff_data.extend_from_slice(&legacy_patch[cursor..cursor + add_len]);
251
+ cursor += add_len;
252
+
253
+ if legacy_patch.len() - cursor < copy_len {
254
+ return Err("legacy patch truncated in extra section".to_string());
255
+ }
256
+ extra_data.extend_from_slice(&legacy_patch[cursor..cursor + copy_len]);
257
+ cursor += copy_len;
258
+
259
+ controls.extend_from_slice(ctrl_bytes);
260
+ }
261
+
262
+ Ok((controls, diff_data, extra_data))
263
+ }
264
+
265
+ fn apply_bsdiff40_patch(old: &[u8], patch: &[u8]) -> Result<Vec<u8>, String> {
266
+ if patch.len() < 32 || &patch[0..8] != b"BSDIFF40" {
267
+ return Err("invalid BSDIFF40 header".to_string());
268
+ }
269
+
270
+ let ctrl_len = read_offt(&patch[8..16])?;
271
+ let diff_len = read_offt(&patch[16..24])?;
272
+ let new_size = read_offt(&patch[24..32])?;
273
+ if ctrl_len < 0 || diff_len < 0 || new_size < 0 {
274
+ return Err("negative BSDIFF40 header values".to_string());
275
+ }
276
+
277
+ let ctrl_len = usize::try_from(ctrl_len).map_err(|_| "control length overflow".to_string())?;
278
+ let diff_len = usize::try_from(diff_len).map_err(|_| "diff length overflow".to_string())?;
279
+ let new_size = usize::try_from(new_size).map_err(|_| "new size overflow".to_string())?;
280
+
281
+ let ctrl_start = 32usize;
282
+ let ctrl_end = ctrl_start
283
+ .checked_add(ctrl_len)
284
+ .ok_or_else(|| "control block overflow".to_string())?;
285
+ let diff_end = ctrl_end
286
+ .checked_add(diff_len)
287
+ .ok_or_else(|| "diff block overflow".to_string())?;
288
+ if diff_end > patch.len() {
289
+ return Err("BSDIFF40 block bounds are invalid".to_string());
290
+ }
291
+
292
+ let mut ctrl_reader = BzDecoder::new(Cursor::new(&patch[ctrl_start..ctrl_end]));
293
+ let mut diff_reader = BzDecoder::new(Cursor::new(&patch[ctrl_end..diff_end]));
294
+ let mut extra_reader = BzDecoder::new(Cursor::new(&patch[diff_end..]));
295
+
296
+ let mut out = Vec::with_capacity(new_size);
297
+ let mut old_pos: i64 = 0;
298
+
299
+ while out.len() < new_size {
300
+ let mut ctrl_buf = [0u8; 24];
301
+ ctrl_reader
302
+ .read_exact(&mut ctrl_buf)
303
+ .map_err(|error| format!("failed to read control block: {error}"))?;
304
+
305
+ let add_len = read_offt(&ctrl_buf[0..8])?;
306
+ let copy_len = read_offt(&ctrl_buf[8..16])?;
307
+ let seek_len = read_offt(&ctrl_buf[16..24])?;
308
+ if add_len < 0 || copy_len < 0 {
309
+ return Err("negative add/copy length in control block".to_string());
310
+ }
311
+
312
+ let add_len = usize::try_from(add_len).map_err(|_| "add length overflow".to_string())?;
313
+ let copy_len =
314
+ usize::try_from(copy_len).map_err(|_| "copy length overflow".to_string())?;
315
+
316
+ let mut diff_bytes = vec![0u8; add_len];
317
+ diff_reader
318
+ .read_exact(&mut diff_bytes)
319
+ .map_err(|error| format!("failed to read diff block: {error}"))?;
320
+ for delta_byte in diff_bytes {
321
+ if old_pos < 0 || old_pos as usize >= old.len() {
322
+ return Err("old file offset out of bounds".to_string());
323
+ }
324
+ let old_byte = old[old_pos as usize];
325
+ out.push(delta_byte.wrapping_add(old_byte));
326
+ old_pos += 1;
327
+ }
328
+
329
+ let mut extra_bytes = vec![0u8; copy_len];
330
+ extra_reader
331
+ .read_exact(&mut extra_bytes)
332
+ .map_err(|error| format!("failed to read extra block: {error}"))?;
333
+ out.extend_from_slice(&extra_bytes);
334
+
335
+ old_pos = old_pos
336
+ .checked_add(seek_len)
337
+ .ok_or_else(|| "old file seek overflow".to_string())?;
338
+
339
+ if out.len() > new_size {
340
+ return Err("patch output exceeds target size".to_string());
341
+ }
342
+ }
343
+
344
+ if out.len() != new_size {
345
+ return Err("patch output length mismatch".to_string());
346
+ }
347
+
348
+ Ok(out)
349
+ }
350
+
351
+ fn bzip2_compress(input: &[u8]) -> Result<Vec<u8>, String> {
352
+ let mut encoder = BzEncoder::new(Vec::new(), Compression::new(6));
353
+ encoder.write_all(input).map_err(|error| format!("{error}"))?;
354
+ encoder.finish().map_err(|error| format!("{error}"))
355
+ }
356
+
357
+ fn read_offt(bytes: &[u8]) -> Result<i64, String> {
358
+ if bytes.len() < 8 {
359
+ return Err("offset bytes too short".to_string());
360
+ }
361
+
362
+ let mut raw = [0u8; 8];
363
+ raw.copy_from_slice(&bytes[0..8]);
364
+ let value = i64::from_le_bytes(raw);
365
+ if value & (1_i64 << 63) == 0 {
366
+ Ok(value)
367
+ } else {
368
+ Ok(-(value & !(1_i64 << 63)))
369
+ }
370
+ }
371
+
372
+ fn write_offt(value: i64, out: &mut Vec<u8>) -> Result<(), String> {
373
+ if value < 0 {
374
+ return Err("negative header value".to_string());
375
+ }
376
+
377
+ let encoded = u64::try_from(value).map_err(|_| "header value overflow".to_string())?;
378
+ out.extend_from_slice(&encoded.to_le_bytes());
379
+ Ok(())
380
+ }
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const rootDir = findProjectRoot(__dirname);
13
+ const configPath = path.join(__dirname, "config.json");
14
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
15
+
16
+ const cargoBin = config.tools?.cargo || "cargo";
17
+ const rustupBin = config.tools?.rustup || "rustup";
18
+ const target = resolveBuildTarget(config.wasm || {});
19
+
20
+ ensureRustTargetInstalled(rustupBin, target.rustTarget);
21
+
22
+ console.log(`[wasm] building ${target.manifestPath}`);
23
+ execFileSync(
24
+ cargoBin,
25
+ ["build", "--manifest-path", target.manifestPath, "--target", target.rustTarget, "--release"],
26
+ { stdio: "inherit" }
27
+ );
28
+
29
+ if (!existsSync(target.sourceWasm)) {
30
+ throw new Error(`[wasm] build succeeded but wasm output is missing: ${target.sourceWasm}`);
31
+ }
32
+
33
+ mkdirSync(path.dirname(target.outputWasm), { recursive: true });
34
+ cpSync(target.sourceWasm, target.outputWasm);
35
+
36
+ console.log(`[wasm] copied ${target.sourceWasm} -> ${target.outputWasm}`);
37
+ console.log(`[wasm] sha256 ${sha256File(target.outputWasm)}`);
38
+
39
+ function resolveBuildTarget(targetConfig) {
40
+ const crateDir = resolvePath(targetConfig.crateDir || "rust/hdiff-wasm");
41
+ const rustTarget = targetConfig.rustTarget || "wasm32-unknown-unknown";
42
+ const manifestPath = resolvePath(targetConfig.manifestPath || path.join(crateDir, "Cargo.toml"));
43
+ const crateName = targetConfig.crateName || readCargoPackageName(manifestPath);
44
+ const wasmFileName = targetConfig.wasmFileName || `${crateName.replaceAll("-", "_")}.wasm`;
45
+
46
+ const sourceWasm = resolvePath(
47
+ targetConfig.sourceWasm || path.join(crateDir, "target", rustTarget, "release", wasmFileName)
48
+ );
49
+
50
+ const outputWasm = resolvePath(targetConfig.outputWasm || "assets/hdiff.wasm");
51
+
52
+ return {
53
+ rustTarget,
54
+ manifestPath,
55
+ sourceWasm,
56
+ outputWasm,
57
+ };
58
+ }
59
+
60
+ function ensureRustTargetInstalled(rustup, target) {
61
+ let installedTargets;
62
+ try {
63
+ installedTargets = execFileSync(rustup, ["target", "list", "--installed"], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ });
67
+ } catch (error) {
68
+ throw new Error(`[wasm] ${rustup} is required to build wasm. Original error: ${String(error)}`);
69
+ }
70
+
71
+ const installed = new Set(installedTargets.split(/\r?\n/).filter(Boolean));
72
+ if (installed.has(target)) {
73
+ return;
74
+ }
75
+
76
+ console.log(`[wasm] installing rust target: ${target}`);
77
+ execFileSync(rustup, ["target", "add", target], { stdio: "inherit" });
78
+ }
79
+
80
+ function readCargoPackageName(manifestPath) {
81
+ const raw = readFileSync(manifestPath, "utf8");
82
+ const packageSection = raw.match(/\[package\]([\s\S]*?)(?:\n\s*\[|$)/)?.[1] ?? "";
83
+ const name = packageSection.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1];
84
+ if (!name) {
85
+ throw new Error(`[wasm] unable to resolve crate name from ${manifestPath}`);
86
+ }
87
+ return name;
88
+ }
89
+
90
+ function resolvePath(value) {
91
+ return path.isAbsolute(value) ? value : path.resolve(rootDir, value);
92
+ }
93
+
94
+ function sha256File(filePath) {
95
+ return createHash("sha256").update(readFileSync(filePath)).digest("hex");
96
+ }
97
+
98
+ function findProjectRoot(startDir) {
99
+ let cursor = startDir;
100
+ while (true) {
101
+ if (existsSync(path.join(cursor, "package.json"))) {
102
+ return cursor;
103
+ }
104
+
105
+ const parent = path.dirname(cursor);
106
+ if (parent === cursor) {
107
+ throw new Error(`[wasm] unable to find project root from ${startDir}`);
108
+ }
109
+ cursor = parent;
110
+ }
111
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "tools": {
3
+ "cargo": "cargo",
4
+ "rustup": "rustup"
5
+ },
6
+ "wasm": {
7
+ "crateDir": "rust/hdiff-wasm",
8
+ "rustTarget": "wasm32-unknown-unknown",
9
+ "outputWasm": "assets/hdiff.wasm"
10
+ }
11
+ }