1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 | import { te, fromHex, Sha1 } from "./util";
import { deflate } from "./zlib";
import { ObjType, TYPE_NUM } from "./objects";
export function applyDelta(base: Uint8Array, delta: Uint8Array): Uint8Array {
let pos = 0;
// bounded read: a truncated/crafted delta must throw, never read past the end
// (which would fold `undefined` into offsets/sizes and corrupt the result).
const next = (): number => {
if (pos >= delta.length) throw new Error("delta truncated");
return delta[pos++];
};
const varint = () => {
let r = 0, shift = 0, b: number;
do {
b = next();
r += (b & 0x7f) * 2 ** shift;
shift += 7;
} while (b & 0x80);
return r;
};
const srcSize = varint();
const tgtSize = varint();
if (srcSize !== base.length) throw new Error("delta base size mismatch");
if (tgtSize > 512 * 1024 * 1024) throw new Error("delta target too large");
const out = new Uint8Array(tgtSize);
let op = 0;
while (pos < delta.length) {
const cmd = next();
if (cmd & 0x80) {
// copy from base
let off = 0, size = 0;
if (cmd & 0x01) off = next();
if (cmd & 0x02) off |= next() << 8;
if (cmd & 0x04) off |= next() << 16;
if (cmd & 0x08) off += next() * 0x1000000;
if (cmd & 0x10) size = next();
if (cmd & 0x20) size |= next() << 8;
if (cmd & 0x40) size |= next() << 16;
if (size === 0) size = 0x10000;
if (off + size > base.length) throw new Error("delta copy out of range");
if (op + size > tgtSize) throw new Error("delta copy overflows target");
out.set(base.subarray(off, off + size), op);
op += size;
} else if (cmd) {
// insert literal
if (pos + cmd > delta.length) throw new Error("delta literal truncated");
if (op + cmd > tgtSize) throw new Error("delta literal overflows target");
out.set(delta.subarray(pos, pos + cmd), op);
op += cmd;
pos += cmd;
} else {
throw new Error("invalid delta opcode 0");
}
}
if (op !== tgtSize) throw new Error("delta target size mismatch");
return out;
}
const REF_DELTA_NUM = 7;
function encodeTypeSizeNum(typeNum: number, size: number): Uint8Array {
const bytes: number[] = [];
let first = (typeNum << 4) | (size & 0x0f);
size = Math.floor(size / 16);
while (size > 0) {
bytes.push(first | 0x80);
first = size & 0x7f;
size = Math.floor(size / 128);
}
bytes.push(first);
return new Uint8Array(bytes);
}
function encodeTypeSize(type: ObjType, size: number): Uint8Array {
return encodeTypeSizeNum(TYPE_NUM[type], size);
}
/**
* Incremental packfile writer: emits raw pack bytes through `emit` while
* keeping the running SHA-1 for the trailer, so packs can be streamed
* without ever materializing the whole file.
*/
export class PackWriter {
private sha = new Sha1();
constructor(private emit: (chunk: Uint8Array) => void) {}
private out(chunk: Uint8Array): void {
this.sha.update(chunk);
this.emit(chunk);
}
header(count: number): void {
const h = new Uint8Array(12);
h.set(te.encode("PACK"), 0);
const dv = new DataView(h.buffer);
dv.setUint32(4, 2);
dv.setUint32(8, count);
this.out(h);
}
object(type: ObjType, data: Uint8Array): void {
this.out(encodeTypeSize(type, data.length));
this.out(deflate(data));
}
/** Copy a stored full entry verbatim (already zlib-compressed). */
rawFull(type: ObjType, entrySize: number, compressed: Uint8Array): void {
this.out(encodeTypeSize(type, entrySize));
this.out(compressed);
}
/** Copy a stored delta entry verbatim, addressed as a ref-delta. */
rawDelta(entrySize: number, baseOid: string, compressed: Uint8Array): void {
this.out(encodeTypeSizeNum(REF_DELTA_NUM, entrySize));
this.out(fromHex(baseOid));
this.out(compressed);
}
finish(): void {
this.emit(this.sha.digest()); // trailer is not part of the hashed content
}
}
|