git ssb

0+

cel / pull-git-remote-helper



Tree: 12cc851c71e6ab09d98c2ce054c7ae44d20f85b3

Files: 12cc851c71e6ab09d98c2ce054c7ae44d20f85b3 / pack.js

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

Built with git-ssb-web