UNPKG

9.62 kBJavaScriptView Raw
1/**
2 * Structure
3 * =========
4 *
5 * A convenient structure to extend objects from that comes with very common
6 * boiler plate instance methods:
7 * - fromObject
8 * - fromBr
9 * - toBw
10 * - fromBuffer
11 * - fromFastBuffer
12 * - toBuffer
13 * - toFastBuffer
14 * - fromHex
15 * - toHex
16 * - fromString
17 * - toString
18 * - fromJSON
19 * - toJSON
20 * - cloneByBuffer
21 * - cloneByFastBuffer
22 * - cloneByHex
23 * - cloneByString
24 * - cloneByJSON
25 *
26 * As well as static methods for:
27 * - fromObject
28 * - fromBr
29 * - fromBuffer
30 * - fromFastBuffer
31 * - fromHex
32 * - fromString
33 * - fromJSON
34 *
35 * The "expect" method also facilitates deserializing a sequence of buffers
36 * into an object.
37 */
38'use strict'
39
40import { Br } from './br'
41import { Bw } from './bw'
42import isHex from 'is-hex'
43
44class Struct {
45 constructor (obj) {
46 this.fromObject(obj)
47 }
48
49 fromObject (obj) {
50 if (!obj) {
51 return this
52 }
53 for (const key of Object.keys(obj)) {
54 if (obj[key] !== undefined) {
55 this[key] = obj[key]
56 }
57 }
58 return this
59 }
60
61 static fromObject (obj) {
62 return new this().fromObject(obj)
63 }
64
65 fromBr (br) {
66 if (!(br instanceof Br)) {
67 throw new Error('br must be a buffer reader')
68 }
69 throw new Error('not implemented')
70 }
71
72 static fromBr (br) {
73 return new this().fromBr(br)
74 }
75
76 asyncFromBr (br) {
77 if (!(br instanceof Br)) {
78 throw new Error('br must be a buffer reader')
79 }
80 throw new Error('not implemented')
81 }
82
83 static asyncFromBr (br) {
84 return new this().asyncFromBr(br)
85 }
86
87 toBw (bw) {
88 throw new Error('not implemented')
89 }
90
91 asyncToBw (bw) {
92 throw new Error('not implemented')
93 }
94
95 /**
96 * It is very often the case that you want to create a bitcoin object from a
97 * stream of small buffers rather than from a buffer of the correct length.
98 * For instance, if streaming from the network or disk. The genFromBuffers
99 * method is a generator which produces an iterator. Use .next(buf) to pass
100 * in a small buffer. The iterator will end when it has received enough data
101 * to produce the object. In some cases it is able to yield the number of
102 * bytes it is expecting, but that is not always known.
103 */
104 * genFromBuffers () {
105 throw new Error('not implemented')
106 }
107
108 /**
109 * A convenience method used by from the genFromBuffers* generators.
110 * Basically lets you expect a certain number of bytes (len) and keeps
111 * yielding until you give it enough. It yields the expected amount
112 * remaining, and returns an object containing a buffer of the expected
113 * length, and, if any, the remainder buffer.
114 */
115 * expect (len, startbuf) {
116 let buf = startbuf
117 const bw = new Bw()
118 let gotlen = 0
119 if (startbuf) {
120 bw.write(startbuf)
121 gotlen += startbuf.length
122 }
123 while (gotlen < len) {
124 const remainderlen = len - gotlen
125 buf = yield remainderlen
126 if (!buf) {
127 continue
128 }
129 bw.write(buf)
130 gotlen += buf.length
131 }
132 buf = bw.toBuffer()
133 const overlen = gotlen - len
134 const remainderbuf = buf.slice(buf.length - overlen, buf.length)
135 buf = buf.slice(0, buf.length - overlen)
136 return {
137 buf: buf,
138 remainderbuf: remainderbuf
139 }
140 }
141
142 /**
143 * Convert a buffer into an object, i.e. deserialize the object.
144 */
145 fromBuffer (buf, ...rest) {
146 if (!Buffer.isBuffer(buf)) {
147 throw new Error('buf must be a buffer')
148 }
149 const br = new Br(buf)
150 return this.fromBr(br, ...rest)
151 }
152
153 static fromBuffer (...rest) {
154 return new this().fromBuffer(...rest)
155 }
156
157 asyncFromBuffer (buf, ...rest) {
158 if (!Buffer.isBuffer(buf)) {
159 throw new Error('buf must be a buffer')
160 }
161 const br = new Br(buf)
162 return this.asyncFromBr(br, ...rest)
163 }
164
165 static asyncFromBuffer (buf, ...rest) {
166 return new this().asyncFromBuffer(buf, ...rest)
167 }
168
169 /**
170 * The complement of toFastBuffer - see description for toFastBuffer
171 */
172 fromFastBuffer (buf, ...rest) {
173 if (buf.length === 0) {
174 return this
175 } else {
176 return this.fromBuffer(buf, ...rest)
177 }
178 }
179
180 static fromFastBuffer (...rest) {
181 return new this().fromFastBuffer(...rest)
182 }
183
184 /**
185 * Convert the object into a buffer, i.e. serialize the object. This method
186 * may block the main thread.
187 */
188 toBuffer (...rest) {
189 return this.toBw(...rest).toBuffer()
190 }
191
192 asyncToBuffer (...rest) {
193 return this.asyncToBw(...rest).then(bw => bw.toBuffer())
194 }
195
196 /**
197 * Sometimes the toBuffer method has cryptography and blocks the main thread,
198 * and we need a non-blocking way to serialize an object. That is what
199 * toFastBuffer is. Of course it defaults to just using toBuffer if an object
200 * hasn't implemented it. If your regular toBuffer method blocks, like with
201 * Bip32, then you should implement this method to be non-blocking. This
202 * method is used to send objects to the workers. i.e., for converting a
203 * Bip32 object to a string, we need to encode it as a buffer in a
204 * non-blocking manner with toFastBuffer, send it to a worker, then the
205 * worker converts it to a string, which is a blocking operation.
206 *
207 * It is very common to want to convert a blank object to a zero length
208 * buffer, so we can transport a blank object to a worker. So that behavior
209 * is included by default.
210 */
211 toFastBuffer (...rest) {
212 if (Object.keys(this).length === 0) {
213 return Buffer.alloc(0)
214 } else {
215 return this.toBuffer(...rest)
216 }
217 }
218
219 fromHex (hex, ...rest) {
220 if (!isHex(hex)) {
221 throw new Error('invalid hex string')
222 }
223 const buf = Buffer.from(hex, 'hex')
224 return this.fromBuffer(buf, ...rest)
225 }
226
227 static fromHex (hex, ...rest) {
228 return new this().fromHex(hex, ...rest)
229 }
230
231 asyncFromHex (hex, ...rest) {
232 if (!isHex(hex)) {
233 throw new Error('invalid hex string')
234 }
235 const buf = Buffer.from(hex, 'hex')
236 return this.asyncFromBuffer(buf, ...rest)
237 }
238
239 static asyncFromHex (hex, ...rest) {
240 return new this().asyncFromHex(hex, ...rest)
241 }
242
243 fromFastHex (hex, ...rest) {
244 if (!isHex(hex)) {
245 throw new Error('invalid hex string')
246 }
247 const buf = Buffer.from(hex, 'hex')
248 return this.fromFastBuffer(buf, ...rest)
249 }
250
251 static fromFastHex (hex, ...rest) {
252 return new this().fromFastHex(hex, ...rest)
253 }
254
255 toHex (...rest) {
256 return this.toBuffer(...rest).toString('hex')
257 }
258
259 asyncToHex (...rest) {
260 return this.asyncToBuffer(...rest).then(buf => buf.toString('hex'))
261 }
262
263 toFastHex (...rest) {
264 return this.toFastBuffer(...rest).toString('hex')
265 }
266
267 fromString (str, ...rest) {
268 if (typeof str !== 'string') {
269 throw new Error('str must be a string')
270 }
271 return this.fromHex(str, ...rest)
272 }
273
274 static fromString (str, ...rest) {
275 return new this().fromString(str, ...rest)
276 }
277
278 asyncFromString (str, ...rest) {
279 if (typeof str !== 'string') {
280 throw new Error('str must be a string')
281 }
282 return this.asyncFromHex(str, ...rest)
283 }
284
285 static asyncFromString (str, ...rest) {
286 return new this().asyncFromString(str, ...rest)
287 }
288
289 toString (...rest) {
290 return this.toHex(...rest)
291 }
292
293 asyncToString (...rest) {
294 return this.asyncToHex(...rest)
295 }
296
297 fromJSON (json) {
298 throw new Error('not implemented')
299 }
300
301 static fromJSON (json, ...rest) {
302 return new this().fromJSON(json, ...rest)
303 }
304
305 asyncFromJSON (json, ...rest) {
306 throw new Error('not implemented')
307 }
308
309 static asyncFromJSON (json, ...rest) {
310 return new this().asyncFromJSON(json, ...rest)
311 }
312
313 toJSON () {
314 var json = {}
315 for (var val in this) {
316 // arrays
317 if (Array.isArray(this[val])) {
318 const arr = []
319 for (var i in this[val]) {
320 if (typeof this[val][i].toJSON === 'function') {
321 arr.push(this[val][i].toJSON())
322 } else {
323 arr.push(JSON.stringify(this[val][i]))
324 }
325 }
326 json[val] = arr
327 // objects
328 } else if (this[val] === null) {
329 json[val] = this[val]
330 } else if (
331 typeof this[val] === 'object' &&
332 typeof this[val].toJSON === 'function'
333 ) {
334 json[val] = this[val].toJSON()
335 // booleans, numbers, and strings
336 } else if (
337 typeof this[val] === 'boolean' ||
338 typeof this[val] === 'number' ||
339 typeof this[val] === 'string'
340 ) {
341 json[val] = this[val]
342 // buffers
343 } else if (Buffer.isBuffer(this[val])) {
344 json[val] = this[val].toString('hex')
345 // map
346 } else if (this[val] instanceof Map) {
347 json[val] = JSON.stringify(this[val])
348 // throw an error for objects that do not implement toJSON
349 } else if (typeof this[val] === 'object') {
350 throw new Error('not implemented')
351 }
352 }
353 return json
354 // throw new Error('not implemented')
355 }
356
357 asyncToJSON () {
358 throw new Error('not implemented')
359 }
360
361 clone () {
362 // TODO: Should this be more intelligent about picking which clone method
363 // to default to?
364 return this.cloneByJSON()
365 }
366
367 cloneByBuffer () {
368 return new this.constructor().fromBuffer(this.toBuffer())
369 }
370
371 cloneByFastBuffer () {
372 return new this.constructor().fromFastBuffer(this.toFastBuffer())
373 }
374
375 cloneByHex () {
376 return new this.constructor().fromHex(this.toHex())
377 }
378
379 cloneByString () {
380 return new this.constructor().fromString(this.toString())
381 }
382
383 cloneByJSON () {
384 return new this.constructor().fromJSON(this.toJSON())
385 }
386}
387
388export { Struct }