Passed
Push — master ( c79e9a...d409a1 )
by Rafael S.
02:16
created

lib/packer.js   A

Size

Lines of Code 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
rs 10
noi 1
c 0
b 0
f 0
1
/**
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 * https://github.com/rochars/byte-data
4
 *
5
 */
6
7
/**
8
 * @type {!Function}
9
 * @private
10
 */
11
const endianness = require("endianness");
12
/**
13
 * @constructor
14
 * @private
15
 */
16
const Integer = require("./integer.js");
17
/**
18
 * @type {!Int8Array}
19
 * @private
20
 */
21
const int8_ = new Int8Array(8);
22
/**
23
 * @type {!Uint32Array}
24
 * @private
25
 */
26
const ui32_ = new Uint32Array(int8_.buffer);
27
/**
28
 * @type {!Float32Array}
29
 * @private
30
 */
31
const f32_ = new Float32Array(int8_.buffer);
32
/**
33
 * @type {!Float64Array}
34
 * @private
35
 */
36
const f64_ = new Float64Array(int8_.buffer);
37
/**
38
 * @type {Function}
39
 * @private
40
 */
41
let reader_;
42
/**
43
 * @type {Function}
44
 * @private
45
 */
46
let writer_;
47
/**
48
 * @type {Object}
49
 * @private
50
 */
51
let gInt_ = {};
52
53
/**
54
 * Turn a byte buffer into what the bytes represent.
55
 * @param {!Array<number|string>|!Uint8Array} buffer An array of bytes.
56
 * @param {!Object} theType The type definition.
57
 * @return {!Array<number>}
58
 * @private
59
 */
60
function fromBytes(buffer, theType) {
61
    // turn to BE if BE
62
    if (theType["be"]) {
63
        endianness(buffer, theType["offset"]);
64
    }
65
    let len = buffer.length;
66
    // unpack the values
67
    let values = [];
68
    len = len - (theType["offset"] - 1);
69
    for (let i=0; i<len; i+=theType["offset"]) {
70
        values.push(reader_(buffer, i));
71
    }
72
    return values;
73
}
74
75
/**
76
 * Turn numbers and strings to bytes.
77
 * @param {!Array<number|string>} values The data.
78
 * @param {!Object} theType The type definition.
79
 * @return {!Array<number|string>} the data as a byte buffer.
80
 * @private
81
 */
82
function toBytes(values, theType) {
83
    let j = 0;
84
    let bytes = [];
85
    let len = values.length;
86
    // pack the values
87
    for(let i=0; i < len; i++) {
88
        j = writer_(bytes, values[i], j);
89
    }
90
    // turn to BE if BE
91
    if (theType["be"]) {
92
        endianness(bytes, theType["offset"]);
93
    }
94
    return bytes;
95
}
96
97
/**
98
 * Read int values from bytes.
99
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
100
 * @param {number} i The index to read.
101
 * @return {number}
102
 * @private
103
 */
104
function readInt_(bytes, i) {
105
    return gInt_.read(bytes, i);
106
}
107
108
/**
109
 * Read 1 16-bit float from bytes.
110
 * Thanks https://stackoverflow.com/a/8796597
111
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
112
 * @param {number} i The index to read.
113
 * @return {number}
114
 * @private
115
 */
116
function read16F_(bytes, i) {
117
    let int = gInt_.read(bytes, i);
118
    let exponent = (int & 0x7C00) >> 10;
119
    let fraction = int & 0x03FF;
120
    let floatValue;
121
    if (exponent) {
122
        floatValue =  Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
123
    } else {
124
        floatValue = 6.103515625e-5 * (fraction / 0x400);
125
    }
126
    return floatValue * (int >> 15 ? -1 : 1);
127
}
128
129
/**
130
 * Read 1 32-bit float from bytes.
131
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
132
 * @param {number} i The index to read.
133
 * @return {number}
134
 * @private
135
 */
136
function read32F_(bytes, i) {
137
    ui32_[0] = gInt_.read(bytes, i);
138
    return f32_[0];
139
}
140
141
/**
142
 * Read 1 64-bit float from bytes.
143
 * Thanks https://gist.github.com/kg/2192799
144
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
145
 * @param {number} i The index to read.
146
 * @return {number}
147
 * @private
148
 */
149
function read64F_(bytes, i) {
150
    ui32_[0] = gInt_.read(bytes, i);
151
    ui32_[1] = gInt_.read(bytes, i + 4);
152
    return f64_[0];
153
}
154
155
/**
156
 * Read 1 char from bytes.
157
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
158
 * @param {number} i The index to read.
159
 * @return {string}
160
 * @private
161
 */
162
function readChar_(bytes, i) {
163
    let chrs = "";
164
    for(let j=0; j < gInt_.offset; j++) {
165
        chrs += String.fromCharCode(bytes[i+j]);
166
    }
167
    return chrs;
168
}
169
170
/**
171
 * Write a integer value to a byte buffer.
172
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
173
 * @param {number} number The number to write as bytes.
174
 * @param {number} j The index being written in the byte buffer.
175
 * @return {!number} The next index to write on the byte buffer.
176
 * @private
177
 */
178
function writeInt_(bytes, number, j) {
179
    return gInt_.write(bytes, number, j);
180
}
181
182
/**
183
 * Write one 16-bit float as a binary value.
184
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
185
 * @param {number} number The number to write as bytes.
186
 * @param {number} j The index being written in the byte buffer.
187
 * @return {number} The next index to write on the byte buffer.
188
 * @private
189
 */
190
function write16F_(bytes, number, j) {
191
    f32_[0] = number;
192
    let x = ui32_[0];
193
    let bits = (x >> 16) & 0x8000;
194
    let m = (x >> 12) & 0x07ff;
195
    let e = (x >> 23) & 0xff;
196
    if (e >= 103) {
197
        bits |= ((e - 112) << 10) | (m >> 1);
198
        bits += m & 1;
199
    }
200
    bytes[j++] = bits & 0xFF;
201
    bytes[j++] = bits >>> 8 & 0xFF;
202
    return j;
203
}
204
205
/**
206
 * Write one 32-bit float as a binary value.
207
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
208
 * @param {number} number The number to write as bytes.
209
 * @param {number} j The index being written in the byte buffer.
210
 * @return {number} The next index to write on the byte buffer.
211
 * @private
212
 */
213
function write32F_(bytes, number, j) {
214
    f32_[0] = number;
215
    return gInt_.write(bytes, ui32_[0], j);
216
}
217
218
/**
219
 * Write one 64-bit float as a binary value.
220
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
221
 * @param {number} number The number to write as bytes.
222
 * @param {number} j The index being written in the byte buffer.
223
 * @return {number} The next index to write on the byte buffer.
224
 * @private
225
 */
226
function write64F_(bytes, number, j) {
227
    f64_[0] = number;
228
    j = gInt_.write(bytes, ui32_[0], j);
229
    return gInt_.write(bytes, ui32_[1], j);
230
}
231
232
/**
233
 * Write one char as a byte.
234
 * @param {!Array<number>|!Uint8Array} bytes An array of bytes.
235
 * @param {string} str The string to write as bytes.
236
 * @param {number} j The index being written in the byte buffer.
237
 * @return {number} The next index to write on the byte buffer.
238
 * @private
239
 */
240
function writeChar_(bytes, str, j) {
241
    for (let i=0; i<str.length; i++) {
242
        bytes[j++] = str.charCodeAt(i);
243
    }
244
    return j;
245
}
246
247
/**
248
 * Set the function to unpack the data.
249
 * @param {!Object} theType The type definition.
250
 * @private
251
 */
252
function setReader_(theType) {
253
    if (theType["float"]) {
254
        if (theType["bits"] == 16) {
255
            reader_ = read16F_;
256
        } else if(theType["bits"] == 32) {
257
            reader_ = read32F_;
258
        } else if(theType["bits"] == 64) {
259
            reader_ = read64F_;
260
        }
261
    } else if (theType["char"]) {
262
        reader_ = readChar_;
263
    } else {
264
        reader_ = readInt_;
265
    }
266
}
267
268
/**
269
 * Set the function to pack the data.
270
 * @param {!Object} theType The type definition.
271
 * @private
272
 */
273
function setWriter_(theType) {
274
    if (theType["float"]) {
275
        if (theType["bits"] == 16) {
276
            writer_ = write16F_;
277
        } else if(theType["bits"] == 32) {
278
            writer_ = write32F_;
279
        } else if(theType["bits"] == 64) {
280
            writer_ = write64F_;
281
        }
282
    } else if (theType["char"]) {
283
        writer_ = writeChar_;
284
    } else {
285
        writer_ = writeInt_;
286
    }   
287
}
288
289
/**
290
 * Validate the type and set up the packing/unpacking functions.
291
 * @param {!Object} theType The type definition.
292
 * @throws {Error} If the type definition is not valid.
293
 * @private
294
 */
295
function setUp(theType) {
296
    validateType_(theType);
297
    theType["offset"] = theType["bits"] < 8 ? 1 : Math.ceil(theType["bits"] / 8);
298
    setReader_(theType);
299
    setWriter_(theType);
300
    if (!theType["char"]) {
301
        let value=0;
0 ignored issues
show
Unused Code introduced by
The variable value seems to be never used. Consider removing it.
Loading history...
302
        gInt_ = new Integer(
303
            theType["bits"] == 64 ? 32 : theType["bits"],
304
            theType["float"] ? false : theType["signed"]);
305
    } else {
306
        // Workaround; should not use Integer when type["char"]
307
        gInt_.offset = theType["bits"] < 8 ? 1 : Math.ceil(theType["bits"] / 8);
308
    }
309
}
310
311
/**
312
 * Validate the type definition.
313
 * @param {!Object} theType The type definition.
314
 * @throws {Error} If the type definition is not valid.
315
 * @private
316
 */
317
function validateType_(theType) {
318
    if (!theType) {
319
        throw new Error("Undefined type.");
320
    }
321
    if (theType["float"]) {
322
        if ([16,32,64].indexOf(theType["bits"]) == -1) {
323
            throw new Error("Not a supported float type.");
324
        }
325
    } else {
326
        if (theType["char"]) {
327
            if (theType["bits"] < 8 || theType["bits"] % 2) {
328
                throw new Error("Wrong offset for type char.");  
329
            }
330
        } else {
331
            if (theType["bits"] < 1 || theType["bits"] > 53) {
332
                throw new Error("Not a supported type.");  
333
            }
334
        }
335
    }
336
}
337
338
/**
339
 * Fix strings with bad length by either truncating when length is
340
 * greater than the defined in the type or turning them to "" if
341
 * length is smaller than defined in the type. If the value is not a
342
 * string, just return the value.
343
 * @param {string|number} value The string to fix.
344
 * @param {!Object} theType The type definition.
345
 * @return {string|number}
346
 * @private
347
 */
348
function fixBadString(value, theType) {
349
    if (value.constructor == String) {
350
        if (value.length > theType["offset"]) {
351
            throw new Error("String is bigger than its type definition.");
352
        } else if (value.length < theType["offset"]) {
353
            throw new Error("String is smaller than its type definition.");
354
        }
355
    }
356
    return value;
357
}
358
359
module.exports.setUp = setUp;
360
module.exports.fixBadString = fixBadString;
361
module.exports.toBytes = toBytes;
362
module.exports.fromBytes = fromBytes;
363