Passed
Branch v11.x (988995)
by Rafael S.
02:49 queued 01:03
created

main.js (1 issue)

1
/*
2
 * byte-data: Pack and unpack binary data.
3
 * https://github.com/rochars/byte-data
4
 *
5
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
 *
26
 */
27
28
/**
29
 * @fileoverview The byte-data API.
30
 */
31
32
/** @module byteData */
33
34
/**
35
 * byte-data standard types.
36
 * @type {!Object}
37
 */
38
export {types} from './lib/types.js';
39
40
/**
41
 * @constructor
42
 */
43
import Integer from './lib/integer';
44
45
/**
46
 * @type {!Function}
47
 * @private
48
 */
49
import {endianness} from 'endianness';
50
/**
51
 * @type {!Int8Array}
52
 * @private
53
 */
54
const int8_ = new Int8Array(8);
55
/**
56
 * @type {!Uint32Array}
57
 * @private
58
 */
59
const ui32_ = new Uint32Array(int8_.buffer);
60
/**
61
 * @type {!Float32Array}
62
 * @private
63
 */
64
const f32_ = new Float32Array(int8_.buffer);
65
/**
66
 * @type {!Float64Array}
67
 * @private
68
 */
69
const f64_ = new Float64Array(int8_.buffer);
70
/**
71
 * @type {Function}
72
 * @private
73
 */
74
let reader_;
75
/**
76
 * @type {Function}
77
 * @private
78
 */
79
let writer_;
80
/**
81
 * @type {Object}
82
 * @private
83
 */
84
let gInt_ = {};
85
86
/**
87
 * Pack a number or a string as a byte buffer.
88
 * @param {number|string} value The value.
89
 * @param {!Object} theType The type definition.
90
 * @return {!Array<number>}
91
 * @throws {Error} If the type definition is not valid.
92
 * @throws {Error} If the value is not valid.
93
 */
94
export function pack(value, theType) {
95
  setUp_(theType);
96
  return toBytes_([value], theType);
97
}
98
99
/**
100
 * Pack an array of numbers or strings to a byte buffer.
101
 * @param {!Array<number|string>} values The values.
102
 * @param {!Object} theType The type definition.
103
 * @return {!Array<number>}
104
 * @throws {Error} If the type definition is not valid.
105
 * @throws {Error} If any of the values are not valid.
106
 */
107
export function packArray(values, theType) {
108
  setUp_(theType);
109
  return toBytes_(values, theType);
110
}
111
112
/**
113
 * Pack a number or a string as a byte buffer.
114
 * @param {number|string} value The value.
115
 * @param {!Object} theType The type definition.
116
 * @param {!Uint8Array|!Array<number>} buffer The output buffer.
117
 * @param {number} index The buffer index to write.
118
 * @return {number} The next index to start writing.
119
 * @throws {Error} If the type definition is not valid.
120
 * @throws {Error} If the value is not valid.
121
 */
122
export function packTo(value, theType, buffer, index) {
123
  setUp_(theType);
124
  let validate = validateNotNull_;
125
  if (theType['char']) {
126
    validate = validateString_;
127
  }
128
  return writeBytes_(value,
129
    theType,
130
    buffer,
131
    index,
132
    index + theType['offset'],
133
    validate,
134
    theType['be']);
135
}
136
137
/**
138
 * Pack a number or a string as a byte buffer.
139
 * @param {number|string} values The value.
140
 * @param {!Object} theType The type definition.
141
 * @param {!Uint8Array|!Array<number>} buffer The output buffer.
142
 * @param {number} index The buffer index to write.
143
 * @return {number} The next index to start writing.
144
 * @throws {Error} If the type definition is not valid.
145
 * @throws {Error} If the value is not valid.
146
 */
147
export function packArrayTo(values, theType, buffer, index) {
148
  setUp_(theType);
149
  let validate = validateNotNull_;
150
  if (theType['char']) {
151
    validate = validateString_;
152
  }
153
  let be = theType['be'];
154
  let offset = theType['offset'];
155
  for (let i=0; i<values.length; i++) {
156
    index = writeBytes_(
157
      values[i],
158
      theType,
159
      buffer,
160
      index,
161
      index + offset,
162
      validate, be);
163
  }
164
  return index;
165
}
166
167
/**
168
 * Unpack a number or a string from a byte buffer.
169
 * @param {!Array<number>|!Uint8Array} buffer The byte buffer.
170
 * @param {!Object} theType The type definition.
171
 * @return {number|string}
172
 * @throws {Error} If the type definition is not valid
173
 */
174
export function unpack(buffer, theType) {
175
  setUp_(theType);
176
  let values = fromBytes_(
177
    buffer.slice(0, theType['offset']), theType);
178
  return values[0];
179
}
180
181
/**
182
 * Unpack an array of numbers or strings from a byte buffer.
183
 * @param {!Array<number>|!Uint8Array} buffer The byte buffer.
184
 * @param {!Object} theType The type definition.
185
 * @return {!Array<number|string>}
186
 * @throws {Error} If the type definition is not valid.
187
 */
188
export function unpackArray(buffer, theType) {
189
  setUp_(theType);
190
  return fromBytes_(buffer, theType);
191
}
192
193
/**
194
 * Unpack a number or a string from a byte buffer.
195
 * @param {!Array<number>|!Uint8Array} buffer The byte buffer.
196
 * @param {!Object} theType The type definition.
197
 * @param {number=} index The buffer index to read.
198
 * @return {number|string}
199
 * @throws {Error} If the type definition is not valid
200
 */
201
export function unpackFrom(buffer, theType, index=0) {
202
  setUp_(theType);
203
  return readBytes_(buffer, theType, index);
204
}
205
206
/**
207
 * Unpack a number or a string from a byte buffer.
208
 * @param {!Array<number>|!Uint8Array} buffer The byte buffer.
209
 * @param {!Object} theType The type definition.
210
 * @param {number=} start The start index. Assumes 0.
211
 * @param {?number=} end The end index. Assumes the array length.
212
 * @return {!Array<number>}
213
 * @throws {Error} If the type definition is not valid
214
 */
215
export function unpackArrayFrom(buffer, theType, start=0, end=null) {
216
  setUp_(theType);
217
  if (theType['be']) {
218
    endianness(buffer, theType['offset']);
219
  }
220
  let len = end || buffer.length;
221
  let values = [];
222
  for (let i=start; i<len; i+=theType['offset']) {
223
    values.push(reader_(buffer, i));
224
  }
225
  if (theType['be']) {
226
    endianness(buffer, theType['offset']);
227
  }
228
  return values;
229
}
230
231
/**
232
 * Turn a byte buffer into what the bytes represent.
233
 * @param {!Array<number|string>|!Uint8Array} buffer An array of bytes.
234
 * @param {!Object} theType The type definition.
235
 * @return {number}
236
 * @private
237
 */
238
function readBytes_(buffer, theType, start) {
239
  if (theType['be']) {
240
    endianness(buffer, theType['offset'], start, start + theType['offset']);
241
  }
242
  let value = reader_(buffer, start);
243
  if (theType['be']) {
244
    endianness(buffer, theType['offset'], start, start + theType['offset']);
245
  }
246
  return value;
247
}
248
249
/**
250
 * Turn a byte buffer into what the bytes represent.
251
 * @param {!Array<number|string>|!Uint8Array} buffer An array of bytes.
252
 * @param {!Object} theType The type definition.
253
 * @return {!Array<number>}
254
 * @private
255
 */
256
function fromBytes_(buffer, theType) {
257
  if (theType['be']) {
258
    endianness(buffer, theType['offset']);
259
  }
260
  let len = buffer.length;
261
  let values = [];
262
  len = len - (theType['offset'] - 1);
263
  for (let i=0; i<len; i+=theType['offset']) {
264
    values.push(reader_(buffer, i));
265
  }
266
  return values;
267
}
268
269
/**
270
 * Turn numbers and strings to bytes.
271
 * @param {!Array<number|string>} values The data.
272
 * @param {!Object} theType The type definition.
273
 * @return {!Array<number|string>} the data as a byte buffer.
274
 * @private
275
 */
276
function toBytes_(values, theType) {
277
  let j = 0;
278
  let bytes = [];
279
  let len = values.length;
280
  let validate = validateNotNull_;
281
  if (theType['char']) {
282
    validate = validateString_;
283
  }
284
  for(let i=0; i < len; i++) {
285
    validate(values[i], theType);
286
    j = writer_(bytes, values[i], j);
287
  }
288
  if (theType['be']) {
289
    endianness(bytes, theType['offset']);
290
  }
291
  return bytes;
292
}
293
294
/**
295
 * Turn numbers and strings to bytes.
296
 * @param {number|string} value The value to be packed.
297
 * @param {!Object} theType The type definition.
298
 * @param {!Object} buffer The buffer to write the bytes to.
299
 * @param {number} index The index to start writing.
300
 * @param {number} len The end index.
301
 * @param {!Function} validate The function used to validate input.
302
 * @param {boolean} be True if big-endian.
303
 * @return {number} the new index to be written.
304
 * @private
305
 */
306
function writeBytes_(value, theType, buffer, index, len, validate, be) {
307
  for(let i=index; i<len; i++) {
308
    validate(value, theType);
309
    i = writer_(buffer, value, i);
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
310
    index = i;
311
  }
312
  if (be) {
313
    endianness(
314
      buffer, theType['offset'], index - theType['offset'], index);
315
  }
316
  return index;
317
}
318
319
/**
320
 * Read int values from bytes.
321
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
322
 * @param {number} i The index to read.
323
 * @return {number}
324
 * @private
325
 */
326
function readInt_(bytes, i) {
327
  return gInt_.read(bytes, i);
328
}
329
330
/**
331
 * Read 1 16-bit float from bytes.
332
 * Thanks https://stackoverflow.com/a/8796597
333
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
334
 * @param {number} i The index to read.
335
 * @return {number}
336
 * @private
337
 */
338
function read16F_(bytes, i) {
339
  let int = gInt_.read(bytes, i);
340
  let exponent = (int & 0x7C00) >> 10;
341
  let fraction = int & 0x03FF;
342
  let floatValue;
343
  if (exponent) {
344
    floatValue =  Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
345
  } else {
346
    floatValue = 6.103515625e-5 * (fraction / 0x400);
347
  }
348
  return floatValue * (int >> 15 ? -1 : 1);
349
}
350
351
/**
352
 * Read 1 32-bit float from bytes.
353
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
354
 * @param {number} i The index to read.
355
 * @return {number}
356
 * @private
357
 */
358
function read32F_(bytes, i) {
359
  ui32_[0] = gInt_.read(bytes, i);
360
  return f32_[0];
361
}
362
363
/**
364
 * Read 1 64-bit float from bytes.
365
 * Thanks https://gist.github.com/kg/2192799
366
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
367
 * @param {number} i The index to read.
368
 * @return {number}
369
 * @private
370
 */
371
function read64F_(bytes, i) {
372
  ui32_[0] = gInt_.read(bytes, i);
373
  ui32_[1] = gInt_.read(bytes, i + 4);
374
  return f64_[0];
375
}
376
377
/**
378
 * Read 1 char from bytes.
379
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
380
 * @param {number} i The index to read.
381
 * @return {string}
382
 * @private
383
 */
384
function readChar_(bytes, i) {
385
  let chrs = '';
386
  for(let j=0; j < gInt_.offset; j++) {
387
    chrs += String.fromCharCode(bytes[i+j]);
388
  }
389
  return chrs;
390
}
391
392
/**
393
 * Write a integer value to a byte buffer.
394
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
395
 * @param {number} number The number to write as bytes.
396
 * @param {number} j The index being written in the byte buffer.
397
 * @return {!number} The next index to write on the byte buffer.
398
 * @private
399
 */
400
function writeInt_(bytes, number, j) {
401
  return gInt_.write(bytes, number, j);
402
}
403
404
/**
405
 * Write one 16-bit float as a binary value.
406
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
407
 * @param {number} number The number to write as bytes.
408
 * @param {number} j The index being written in the byte buffer.
409
 * @return {number} The next index to write on the byte buffer.
410
 * @private
411
 */
412
function write16F_(bytes, number, j) {
413
  f32_[0] = number;
414
  let x = ui32_[0];
415
  let bits = (x >> 16) & 0x8000;
416
  let m = (x >> 12) & 0x07ff;
417
  let e = (x >> 23) & 0xff;
418
  if (e >= 103) {
419
    bits |= ((e - 112) << 10) | (m >> 1);
420
    bits += m & 1;
421
  }
422
  bytes[j++] = bits & 0xFF;
423
  bytes[j++] = bits >>> 8 & 0xFF;
424
  return j;
425
}
426
427
/**
428
 * Write one 32-bit float as a binary value.
429
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
430
 * @param {number} number The number to write as bytes.
431
 * @param {number} j The index being written in the byte buffer.
432
 * @return {number} The next index to write on the byte buffer.
433
 * @private
434
 */
435
function write32F_(bytes, number, j) {
436
  f32_[0] = number;
437
  return gInt_.write(bytes, ui32_[0], j);
438
}
439
440
/**
441
 * Write one 64-bit float as a binary value.
442
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
443
 * @param {number} number The number to write as bytes.
444
 * @param {number} j The index being written in the byte buffer.
445
 * @return {number} The next index to write on the byte buffer.
446
 * @private
447
 */
448
function write64F_(bytes, number, j) {
449
  f64_[0] = number;
450
  j = gInt_.write(bytes, ui32_[0], j);
451
  return gInt_.write(bytes, ui32_[1], j);
452
}
453
454
/**
455
 * Write one char as a byte.
456
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
457
 * @param {string} str The string to write as bytes.
458
 * @param {number} j The index being written in the byte buffer.
459
 * @return {number} The next index to write on the byte buffer.
460
 * @private
461
 */
462
function writeChar_(bytes, str, j) {
463
  for (let i=0; i<str.length; i++) {
464
    bytes[j++] = str.charCodeAt(i);
465
  }
466
  return j;
467
}
468
469
/**
470
 * Set the function to unpack the data.
471
 * @param {!Object} theType The type definition.
472
 * @private
473
 */
474
function setReader(theType) {
475
  if (theType['float']) {
476
    if (theType['bits'] == 16) {
477
      reader_ = read16F_;
478
    } else if(theType['bits'] == 32) {
479
      reader_ = read32F_;
480
    } else if(theType['bits'] == 64) {
481
      reader_ = read64F_;
482
    }
483
  } else if (theType['char']) {
484
    reader_ = readChar_;
485
  } else {
486
    reader_ = readInt_;
487
  }
488
}
489
490
/**
491
 * Set the function to pack the data.
492
 * @param {!Object} theType The type definition.
493
 * @private
494
 */
495
function setWriter(theType) {
496
  if (theType['float']) {
497
    if (theType['bits'] == 16) {
498
      writer_ = write16F_;
499
    } else if(theType['bits'] == 32) {
500
      writer_ = write32F_;
501
    } else if(theType['bits'] == 64) {
502
      writer_ = write64F_;
503
    }
504
  } else if (theType['char']) {
505
    writer_ = writeChar_;
506
  } else {
507
    writer_ = writeInt_;
508
  }   
509
}
510
511
/**
512
 * Validate the type and set up the packing/unpacking functions.
513
 * @param {!Object} theType The type definition.
514
 * @throws {Error} If the type definition is not valid.
515
 * @private
516
 */
517
function setUp_(theType) {
518
  validateType_(theType);
519
  theType['offset'] = theType['bits'] < 8 ? 1 : Math.ceil(theType['bits'] / 8);
520
  setReader(theType);
521
  setWriter(theType);
522
  if (!theType['char']) {
523
    gInt_ = new Integer(
524
      theType['bits'] == 64 ? 32 : theType['bits'],
525
      theType['float'] ? false : theType['signed']);
526
  } else {
527
    // Workaround; should not use Integer when type['char']
528
    gInt_.offset = theType['bits'] < 8 ? 1 : Math.ceil(theType['bits'] / 8);
529
  }
530
}
531
532
/**
533
 * Validate the type definition.
534
 * @param {!Object} theType The type definition.
535
 * @throws {Error} If the type definition is not valid.
536
 * @private
537
 */
538
function validateType_(theType) {
539
  if (!theType) {
540
    throw new Error('Undefined type.');
541
  }
542
  if (theType['float']) {
543
    validateFloatType_(theType);
544
  } else {
545
    if (theType['char']) {
546
      validateCharType_(theType);
547
    } else {
548
      validateIntType_(theType);
549
    }
550
  }
551
}
552
553
/**
554
 * Validate the type definition of floating point numbers.
555
 * @param {!Object} theType The type definition.
556
 * @throws {Error} If the type definition is not valid.
557
 * @private
558
 */
559
function validateFloatType_(theType) {
560
  if ([16,32,64].indexOf(theType['bits']) == -1) {
561
    throw new Error('Not a supported float type.');
562
  }
563
}
564
565
/**
566
 * Validate the type definition of char and strings.
567
 * @param {!Object} theType The type definition.
568
 * @throws {Error} If the type definition is not valid.
569
 * @private
570
 */
571
function validateCharType_(theType) {
572
  if (theType['bits'] < 8 || theType['bits'] % 2) {
573
    throw new Error('Wrong offset for type char.');
574
  }
575
}
576
577
/**
578
 * Validate the type definition of integers.
579
 * @param {!Object} theType The type definition.
580
 * @throws {Error} If the type definition is not valid.
581
 * @private
582
 */
583
function validateIntType_(theType) {
584
  if (theType['bits'] < 1 || theType['bits'] > 53) {
585
    throw new Error('Not a supported type.');
586
  }
587
}
588
589
/**
590
 * Validate strings with bad length.
591
 * @param {string|number} value The string to validate.
592
 * @param {!Object} theType The type definition.
593
 * @private
594
 */
595
function validateString_(value, theType) {
596
  validateNotNull_(value);
597
  if (value.length > theType['offset']) {
598
    throw new Error('String is bigger than its type definition.');
599
  } else if (value.length < theType['offset']) {
600
    throw new Error('String is smaller than its type definition.');
601
  }
602
}
603
/**
604
 * Validate that the value is not null.
605
 * @param {string|number} value The value.
606
 * @private
607
 */
608
function validateNotNull_(value) {
609
  if (value === null || value === undefined) {
610
    throw new Error('Cannot pack null or undefined values.');
611
  }
612
}
613