Files: 20c102b7036f7fee19f151e269d33cd7da837a3b / pack.js
5387 bytesRaw
1 | var buffered = require('pull-buffered') |
2 | var pull = require('pull-stream') |
3 | var toPull = require('stream-to-pull-stream') |
4 | var Inflate = require('pako/lib/inflate').Inflate |
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 | |
18 | function error(cb) { |
19 | return function (err) { |
20 | cb(err || true) |
21 | } |
22 | } |
23 | |
24 | function inflateBytes(read) { |
25 | var inflate = new Inflate() |
26 | var ended, dataOut |
27 | |
28 | inflate.onData = function (data) { |
29 | dataOut = new Buffer(data) |
30 | // console.error('inflated data', data.length) |
31 | } |
32 | |
33 | inflate.onEnd = function (status) { |
34 | ended = (status === 0) ? true : new Error(inflate.msg) |
35 | // console.error('inflated end', status, ended) |
36 | } |
37 | |
38 | return function (abort, cb) { |
39 | if (ended) return cb(ended) |
40 | read(abort, function next(end, data) { |
41 | if (end === true) { |
42 | end = null |
43 | data = [] |
44 | } |
45 | if (ended = end) return cb(end) |
46 | if (data.length > 1) return cb(new Error('got more than one byte')) |
47 | dataOut = null |
48 | inflate.push(data, end === true) |
49 | if (dataOut) |
50 | cb(null, dataOut) |
51 | else if (ended) |
52 | cb(ended) |
53 | else |
54 | read(null, next) |
55 | }) |
56 | } |
57 | } |
58 | |
59 | function decodePack(onEnd, read) { |
60 | if (read === undefined) |
61 | return decodePack.bind(this, onEnd) |
62 | |
63 | var ended |
64 | var numObjects = -1 |
65 | var checksum = createHash('sha1') |
66 | var b = buffered(read) |
67 | // TODO: optimize to pass through buffers to checksum |
68 | var readByte = checksum(b.chunks(1)) |
69 | var readWord = checksum(b.chunks(4)) |
70 | var readChecksum = b.chunks(20) |
71 | var expectChecksum = true |
72 | var opts = { |
73 | verbosity: 2 |
74 | } |
75 | |
76 | function readHeader(cb) { |
77 | readWord(null, function (end, header) { |
78 | if (ended = end) return cb(end) |
79 | if (!header.equals(header, new Buffer('PACK'))) |
80 | read(new Error('Invalid packfile header'), error(cb)) |
81 | else |
82 | readVersion(cb) |
83 | }) |
84 | } |
85 | |
86 | function readVersion(cb) { |
87 | readWord(null, function (end, word) { |
88 | if (ended = end) return cb(end) |
89 | var version = word.readUInt32BE() |
90 | if (version < 2 || version > 3) |
91 | read(new Error('Invalid packfile version ' + version), error(cb)) |
92 | else |
93 | readNumObjects(cb) |
94 | }) |
95 | } |
96 | |
97 | function readNumObjects(cb) { |
98 | readWord(null, function (end, word) { |
99 | if (ended = end) return cb(end) |
100 | numObjects = word.readUInt32BE() |
101 | if (opts.verbosity >= 1) |
102 | console.error(numObjects + ' objects') |
103 | readObject(null, cb) |
104 | }) |
105 | } |
106 | |
107 | function readVarInt(cb) { |
108 | var type, value, shift |
109 | // https://codewords.recurse.com/images/three/varint.svg |
110 | readByte(null, function (end, buf) { |
111 | if (ended = end) return cb(end) |
112 | var firstByte = buf[0] |
113 | type = objectTypes[(firstByte >> 4) & 7] |
114 | value = firstByte & 15 |
115 | // console.error('byte1', firstByte, firstByte.toString(2), value, value.toString(2)) |
116 | shift = 4 |
117 | checkByte(firstByte) |
118 | }) |
119 | |
120 | function checkByte(byte) { |
121 | if (byte & 0x80) |
122 | readByte(null, gotByte) |
123 | else |
124 | cb(null, type, value) |
125 | } |
126 | |
127 | function gotByte(end, buf) { |
128 | if (ended = end) return cb(end) |
129 | var byte = buf[0] |
130 | value += (byte & 0x7f) << shift |
131 | shift += 7 |
132 | // console.error('byte', byte, byte.toString(2), value, value.toString(2)) |
133 | checkByte(byte) |
134 | } |
135 | } |
136 | |
137 | function getObject(cb) { |
138 | readVarInt(function (end, type, length) { |
139 | if (opts.verbosity >= 2) |
140 | console.error('read var int', end, type, length) |
141 | numObjects-- |
142 | if (end === true && expectChecksum) |
143 | onEnd(new Error('Missing checksum')) |
144 | if (ended = end) return cb(end) |
145 | // TODO: verify that the inflated data is the correct length |
146 | cb(null, type, length, inflateBytes(readByte)) |
147 | }) |
148 | } |
149 | |
150 | function readTrailer(cb) { |
151 | // read the checksum before it updates to include the trailer |
152 | var expected = checksum.digest() |
153 | readChecksum(null, function (end, value) { |
154 | cb(true) |
155 | if (end === true && expectChecksum) |
156 | onEnd(new Error('Missing checksum')) |
157 | if (!value.equals(expected)) { |
158 | onEnd(new Error('Checksum mismatch: ' + |
159 | expected.hexSlice() + ' != ' + value.hexSlice())) |
160 | } else { |
161 | if (opts.verbosity >= 2) |
162 | console.error('checksum ok', expected.hexSlice()) |
163 | onEnd(null) |
164 | } |
165 | }) |
166 | } |
167 | |
168 | function readObject(abort, cb) { |
169 | if (ended) cb(ended) |
170 | else if (abort) read(abort) |
171 | else if (numObjects < 0) readHeader(cb) |
172 | else if (numObjects > 0) getObject(cb) |
173 | else if (expectChecksum) readTrailer(cb) |
174 | } |
175 | |
176 | return readObject |
177 | } |
178 | |
179 | function encodePack(numObjects, readObject) { |
180 | var ended |
181 | var header = new Buffer(12) |
182 | header.write('PACK') |
183 | header.writeUInt32BE(PACK_VERSION, 4) |
184 | header.writeUInt32BE(numObjects, 8) |
185 | var checksum = createHash('sha1') |
186 | |
187 | return pull.through(function (data) { |
188 | console.error('> ' + data.length, data.toString()) |
189 | })(cat([ |
190 | checksum(cat([ |
191 | pull.once(header), |
192 | function (abort, cb) { |
193 | if (ended) return cb(ended) |
194 | readObject(abort, function (end, type, length, read) { |
195 | if (ended = end) return cb(end) |
196 | console.error('TODO: encode object') |
197 | }) |
198 | } |
199 | ])), |
200 | checksum.readDigest |
201 | ])) |
202 | } |
203 |
Built with git-ssb-web