Files: b57e8b80419ea2fb7cf7d3b5281d371a2b3976c0 / pack.js
7609 bytesRaw
1 | var buffered = require('pull-buffered') |
2 | var pull = require('pull-stream') |
3 | var toPull = require('stream-to-pull-stream') |
4 | var pako = require('pako') |
5 | var createHash = require('./util').createHash |
6 | var cat = require('pull-cat') |
7 | |
8 | exports.decode = decodePack |
9 | exports.encode = encodePack |
10 | |
11 | var PACK_VERSION = 2 |
12 | |
13 | var objectTypes = [ |
14 | 'none', 'commit', 'tree', 'blob', |
15 | 'tag', 'unused', 'ofs-delta', 'ref-delta' |
16 | ] |
17 | var objectTypeNums = { |
18 | commit: 1, |
19 | tree: 2, |
20 | blob: 3, |
21 | tag: 4 |
22 | } |
23 | |
24 | function error(cb) { |
25 | return function (err) { |
26 | cb(err || true) |
27 | } |
28 | } |
29 | |
30 | function inflateBytes(read) { |
31 | var inflate = new pako.Inflate() |
32 | var ended, dataOut |
33 | |
34 | inflate.onData = function (data) { |
35 | dataOut = new Buffer(data) |
36 | // console.error('inflated data', data.length) |
37 | } |
38 | |
39 | inflate.onEnd = function (status) { |
40 | ended = (status === 0) ? true : new Error(inflate.msg) |
41 | // console.error('inflated end', status, ended) |
42 | } |
43 | |
44 | return function (abort, cb) { |
45 | if (ended) return cb(ended) |
46 | read(abort, function next(end, data) { |
47 | if (end === true) { |
48 | end = null |
49 | data = [] |
50 | } |
51 | if (ended = end) return cb(end) |
52 | if (data.length > 1) return cb(new Error('got more than one byte')) |
53 | dataOut = null |
54 | inflate.push(data, end === true) |
55 | if (dataOut) |
56 | cb(null, dataOut) |
57 | else if (ended) |
58 | cb(ended) |
59 | else |
60 | read(null, next) |
61 | }) |
62 | } |
63 | } |
64 | |
65 | function deflate(read) { |
66 | var def = new pako.Deflate() |
67 | var queue = [] |
68 | var ended |
69 | |
70 | def.onData = function (data) { |
71 | queue.push([null, new Buffer(data)]) |
72 | } |
73 | |
74 | def.onEnd = function (status) { |
75 | queue.push([(status === 0) ? true : new Error(def.msg)]) |
76 | } |
77 | |
78 | return function readOut(abort, cb) { |
79 | /* |
80 | function _cb(end, data) { |
81 | console.error('sending deflated', end, |
82 | data && JSON.stringify(data.toString())) |
83 | cb(end, data) |
84 | } |
85 | */ |
86 | if (ended) |
87 | cb(ended) |
88 | else if (queue.length) |
89 | cb.apply(this, queue.shift()) |
90 | else |
91 | read(abort, function next(end, data) { |
92 | if (data) |
93 | console.error('read into deflat', data.length, JSON.stringify(data)) |
94 | if (end === true) def.push([], true) |
95 | else if (end) return cb(end) |
96 | else def.push(data) |
97 | readOut(null, cb) |
98 | }) |
99 | } |
100 | } |
101 | |
102 | function decodePack(onEnd, read) { |
103 | if (read === undefined) |
104 | return decodePack.bind(this, onEnd) |
105 | |
106 | var ended |
107 | var numObjects = -1 |
108 | var checksum = createHash('sha1') |
109 | var b = buffered(read) |
110 | // TODO: optimize to pass through buffers to checksum |
111 | var readByte = checksum(b.chunks(1)) |
112 | var readWord = checksum(b.chunks(4)) |
113 | var readChecksum = b.chunks(20) |
114 | var expectChecksum = true |
115 | var opts = { |
116 | verbosity: 2 |
117 | } |
118 | |
119 | function readHeader(cb) { |
120 | readWord(null, function (end, header) { |
121 | if (ended = end) return cb(end) |
122 | if (!header.equals(header, new Buffer('PACK'))) |
123 | read(new Error('Invalid packfile header'), error(cb)) |
124 | else |
125 | readVersion(cb) |
126 | }) |
127 | } |
128 | |
129 | function readVersion(cb) { |
130 | readWord(null, function (end, word) { |
131 | if (ended = end) return cb(end) |
132 | var version = word.readUInt32BE() |
133 | if (version < 2 || version > 3) |
134 | read(new Error('Invalid packfile version ' + version), error(cb)) |
135 | else |
136 | readNumObjects(cb) |
137 | }) |
138 | } |
139 | |
140 | function readNumObjects(cb) { |
141 | readWord(null, function (end, word) { |
142 | if (ended = end) return cb(end) |
143 | numObjects = word.readUInt32BE() |
144 | if (opts.verbosity >= 1) |
145 | console.error(numObjects + ' objects') |
146 | readObject(null, cb) |
147 | }) |
148 | } |
149 | |
150 | function readVarInt(cb) { |
151 | var type, value, shift |
152 | // https://codewords.recurse.com/images/three/varint.svg |
153 | readByte(null, function (end, buf) { |
154 | if (ended = end) return cb(end) |
155 | var firstByte = buf[0] |
156 | type = objectTypes[(firstByte >> 4) & 7] |
157 | value = firstByte & 15 |
158 | console.error('byte1', firstByte, firstByte.toString(2), value, value.toString(2)) |
159 | shift = 4 |
160 | checkByte(firstByte) |
161 | }) |
162 | |
163 | function checkByte(byte) { |
164 | if (byte & 0x80) |
165 | readByte(null, gotByte) |
166 | else |
167 | cb(null, type, value) |
168 | } |
169 | |
170 | function gotByte(end, buf) { |
171 | if (ended = end) return cb(end) |
172 | var byte = buf[0] |
173 | value += (byte & 0x7f) << shift |
174 | shift += 7 |
175 | console.error('byte', byte, byte.toString(2), value, value.toString(2)) |
176 | checkByte(byte) |
177 | } |
178 | } |
179 | |
180 | function getObject(cb) { |
181 | readVarInt(function (end, type, length) { |
182 | if (opts.verbosity >= 2) |
183 | console.error('read var int', end, type, length) |
184 | numObjects-- |
185 | if (end === true && expectChecksum) |
186 | onEnd(new Error('Missing checksum')) |
187 | if (ended = end) return cb(end) |
188 | // TODO: verify that the inflated data is the correct length |
189 | cb(null, type, length, inflateBytes(readByte)) |
190 | }) |
191 | } |
192 | |
193 | function readTrailer(cb) { |
194 | // read the checksum before it updates to include the trailer |
195 | var expected = checksum.digest() |
196 | readChecksum(null, function (end, value) { |
197 | cb(true) |
198 | if (end === true && expectChecksum) |
199 | onEnd(new Error('Missing checksum')) |
200 | if (!value.equals(expected)) { |
201 | onEnd(new Error('Checksum mismatch: ' + |
202 | expected.hexSlice() + ' != ' + value.hexSlice())) |
203 | } else { |
204 | if (opts.verbosity >= 2) |
205 | console.error('checksum ok', expected.hexSlice()) |
206 | onEnd(null) |
207 | } |
208 | }) |
209 | } |
210 | |
211 | function readObject(abort, cb) { |
212 | if (ended) cb(ended) |
213 | else if (abort) read(abort) |
214 | else if (numObjects < 0) readHeader(cb) |
215 | else if (numObjects > 0) getObject(cb) |
216 | else if (expectChecksum) readTrailer(cb) |
217 | } |
218 | |
219 | return readObject |
220 | } |
221 | |
222 | function once(read) { |
223 | var done |
224 | return function (abort, cb) { |
225 | if (done) cb(done) |
226 | else done = true, read(abort, cb) |
227 | } |
228 | } |
229 | |
230 | function encodeVarInt(typeStr, length, cb) { |
231 | var type = objectTypeNums[typeStr] |
232 | // console.error('TYPE', type, typeStr, 'len', length, typeof cb) |
233 | if (!type) |
234 | return cb(new Error("Bad object type " + typeStr)) |
235 | |
236 | var vals = [] |
237 | var b = (type << 4) | (length & 15) |
238 | for (length >>= 4; length; length >>= 7) { |
239 | vals.push(b | 0x80) |
240 | b = length & 0x7f |
241 | } |
242 | vals.push(b) |
243 | console.error('sending var int', vals, vals.map(function (n) { |
244 | return ('00000000' + Number(n).toString(2)).substr(-8) |
245 | })) |
246 | cb(null, new Buffer(vals)) |
247 | } |
248 | |
249 | /* |
250 | function flow(read) { |
251 | return function (abort, cb) { |
252 | read(abort, cb, function nextCb(newRead, onEnd) { |
253 | read = newRead |
254 | return function (end, data) { |
255 | cb(end, data) |
256 | if (end) onEnd(end === true ? null : end) |
257 | } |
258 | }) |
259 | } |
260 | } |
261 | */ |
262 | |
263 | function encodePack(numObjects, readObject) { |
264 | // var ended |
265 | var header = new Buffer(12) |
266 | header.write('PACK') |
267 | header.writeUInt32BE(PACK_VERSION, 4) |
268 | header.writeUInt32BE(numObjects, 8) |
269 | var checksum = createHash('sha1') |
270 | var readData |
271 | |
272 | /* |
273 | return pull.through(function (data) { |
274 | console.error('> ' + data.length, JSON.stringify(data.toString('ascii'))) |
275 | })(cat([ |
276 | */ |
277 | return cat([ |
278 | checksum(cat([ |
279 | pull.once(header), |
280 | encodeObject |
281 | ])), |
282 | checksum.readDigest |
283 | ]) |
284 | // ) |
285 | |
286 | function encodeObject(abort, cb) { |
287 | if (readData) |
288 | readData(abort, function (end, data) { |
289 | if (end === true) |
290 | readObject(abort, nextObject) |
291 | else |
292 | cb(end, data) |
293 | }) |
294 | else |
295 | readObject(abort, nextObject) |
296 | |
297 | function nextObject(end, type, length, read) { |
298 | // console.error('got obj', end, type, length) |
299 | if (end) return cb(end) |
300 | readData = deflate(read) |
301 | encodeVarInt(type, length, cb) // nextCb(deflate(read), encodeObject)) |
302 | } |
303 | } |
304 | } |
305 |
Built with git-ssb-web