UNPKG

8.37 kBJavaScriptView Raw
1/**
2 * Support arbitrary lenght unsigned integer number convertion to string representation
3 * in the specified radix (base) in Node.js.
4 *
5 * JavaScript Numbers are IEEE-754 binary double-precision floats, which limits the
6 * range of values that can be represented with integer precision to:
7 *
8 * 2^53 <= N <= 2^53
9 *
10 * For more details about IEEE-754 see: http://en.wikipedia.org/wiki/IEEE_floating_point
11 */
12 /*jslint bitwise: true */
13(function () {
14 "use strict";
15
16 var FORMATS = {
17 'dec': toDecimalString,
18 'hex': toHexString,
19 'bin': toBinaryString,
20 'oct': toOctetString
21 };
22
23 module.exports = function (buffer, base, options) {
24 base = base || 'dec';
25 var buf = _toBuffer(buffer), format = FORMATS[base];
26
27 if (format) {
28 return format(buf, options);
29 }
30 };
31
32 function createBuffer(size) {
33 var buf;
34 if (Buffer.alloc) {
35 buf = Buffer.alloc(size);
36 } else {
37 buf = new Buffer(size);
38 buf.fill(0);
39 }
40 return buf;
41 }
42
43 function createBufferFrom(object) {
44 var buf;
45 if (Buffer.from) {
46 buf = Buffer.from(object);
47 } else {
48 buf = new Buffer(object);
49 }
50 return buf;
51 }
52
53 function toDecimalString(buffer, options) {
54 options = options || {};
55 var bits = buffer.length * 8, // number of bits in the buffer
56 lastBit = buffer.length - 1, // last bit index
57 digits = createBuffer(Math.floor(bits / 3 + 1 + 1)), // digits buffer
58 lastDigit = digits.length - 1, carry; // last digit index, digit index, carry flag
59
60 // reset digits buffer
61 digits.fill(0);
62
63 // reverse buffer if not in LE format
64 if ((options.format || 'BE') !== 'LE') {
65 _reverseBuffer(buffer);
66 }
67
68 for (var i = 0; i < bits; i++) {
69 carry = buffer[lastBit] >= 0x80;
70
71 _leftShift(buffer); // shift buffer bits
72
73 for (var d = lastDigit; d >= 0; d--) {
74 digits[d] += digits[d] + (carry ? 1 : 0);
75 carry = (digits[d] > 9);
76 if (carry) {
77 digits[d] -= 10;
78 }
79 }
80 }
81
82 // get rid of leading 0's; reuse d for the first non-zero value index
83 var idx = _lastHeadIndex(digits, 0);
84
85 // if there are only 0's use the last digit
86 idx = idx >= 0 ? idx : lastDigit;
87
88 // convert numbers to ascii digits
89 _toAsciiDigits(digits, idx);
90
91 return _pad(
92 _split(digits.toString('ascii', idx), options.groupsize, options.delimiter),
93 '', options.padstr, options.size);
94 }
95
96 function toBinaryString(buffer, options) {
97 options = options || {};
98 var digits = new Array(buffer.length),
99 size = options.groupsize || -1, num,
100 prefix = options.prefix || '',
101 output;
102
103 if ((options.format || 'BE') !== 'BE') {
104 _reverseBuffer(buffer);
105 }
106
107 for (var i = 0; i < buffer.length; i++) {
108 num = buffer[i].toString(2);
109 digits[i] = '00000000'.slice(0, 8 - num.length) + buffer[i].toString(2);
110 }
111
112 output = digits.join('');
113
114 if (options.trim) {
115 output = output.substr(output.indexOf('1'));
116 }
117
118 if (size > 0) {
119 output = _split(output, size, options.delimiter);
120 }
121
122 return prefix + _pad(output, prefix, options.padstr, options.size);
123 }
124
125 /*
126 * Converts given input (node Buffer or array of bytes) to hexadecimal string 0xDDDD where D is [0-9a-f].
127 * All leading 0's are stripped out i.e. [0x00, 0x00, 0x00, 0x01] -> '0x1'
128 */
129 function toHexString(buffer, options) {
130 options = options || {};
131 var prefix = options.prefix || '', digits, idx;
132
133 if ((options.format || 'BE') !== 'BE') {
134 _reverseBuffer(buffer);
135 }
136
137 digits = buffer.toString('hex');
138 idx = _lastHeadIndex(digits, '0');
139
140 // if there are only 0's use the last digit
141 idx = idx >= 0 ? idx : digits.length - 1;
142
143 return prefix + _pad(
144 _split(digits.slice(idx), options.groupsize, options.delimiter), prefix, options.padstr, options.size);
145 }
146
147 function toOctetString(buffer, options) {
148 options = options || {};
149 var shifts = Math.floor(buffer.length * 8 / 3),
150 lastIdx = buffer.length - 1,
151 digits = createBuffer(shifts),
152 prefix = options.prefix || '', idx;
153
154 digits.fill(0); // reset digits buffer
155 if ((options.format || 'BE') !== 'BE') {
156 _reverseBuffer(buffer);
157 }
158
159 for (var i = digits.length - 1; i >= 0; i--) {
160 digits[i] = buffer[lastIdx] & 0x7;
161
162 // right shift buffer by 3 bits
163 _rightShift(buffer);
164 _rightShift(buffer);
165 _rightShift(buffer);
166 }
167
168 // get rid of leading 0's; reuse d for the first non-zero value index
169 idx = _lastHeadIndex(digits, 0);
170 idx = idx >= 0 ? idx : lastIdx;
171
172 // convert numbers to ascii digits
173 _toAsciiDigits(digits, idx);
174
175 return prefix + _pad(
176 _split(digits.toString('ascii', idx), options.groupsize, options.delimiter), prefix, options.padstr, options.size);
177 }
178
179 function _split(string, size, delim) {
180 if (typeof delim === 'undefined') {
181 delim = ' ';
182 }
183 if (typeof string !== 'undefined' && +size > 0) {
184 string = string.replace(new RegExp('(.)(?=(.{' + +size + '})+(?!.))', 'g'), "$1" + delim);
185 }
186 return string;
187 }
188
189 function _pad(str, prefix, pad, size) {
190 if ('undefined' === typeof pad || 'undefined' === typeof size || pad.length === 0 || str.length + prefix.length >= size) {
191 return str;
192 }
193 var padlen = size - str.length - prefix.length;
194 return new Array(Math.ceil(padlen / pad.length) + 1).join(pad).substr(0, padlen) + str;
195 }
196
197 function _toAsciiDigits(buffer, offset) {
198 for (var i = offset; i < buffer.length; i++) {
199 buffer[i] += 48;
200 }
201 }
202
203 /*
204 * Finds last head index of the given value in the given buffer
205 * Otherwise it returns -1.
206 */
207 function _lastHeadIndex(buffer, value) {
208 for (var i = 0; i < buffer.length; i++) {
209 if (buffer[i] !== value) {
210 return i;
211 }
212 }
213 return -1;
214 }
215
216 /*
217 * Checks type of data and perform conversion if necessary
218 */
219 function _toBuffer(buffer) {
220 var _buffer, nums;
221
222 if (Buffer.isBuffer(buffer) || Array.isArray(buffer)) {
223 _buffer = createBufferFrom(buffer);
224 }
225 else if (typeof buffer === 'string') {
226 nums = buffer.replace(/^0x/i, '').match(/.{1,2}(?=(..)+(?!.))|..?$/g);
227 _buffer = createBuffer(nums.length);
228
229 for (var i = nums.length - 1; i >= 0; i--) {
230 _buffer.writeUInt8(parseInt(nums[i], 16), i);
231 }
232 }
233
234 return _buffer;
235 }
236
237 /*
238 * Performs byte order reverse
239 */
240 function _reverseBuffer(buffer) {
241 var tmp, len = buffer.length - 1, half = Math.floor(buffer.length / 2);
242 for (var i = len; i >= half; i--) {
243 tmp = buffer[i];
244 buffer[i] = buffer[len - i];
245 buffer[len - i] = tmp;
246 }
247 }
248
249 /*
250 * Performs buffer left bits shift
251 */
252 function _leftShift(buffer) {
253 var carry;
254 for (var i = buffer.length; i >= 0; i--) {
255 carry = (buffer[i] & 0x80) !== 0;
256 buffer[i] = (buffer[i] << 1) & 0xFF;
257 if (carry && i >= 0) {
258 buffer[i + 1] |= 0x01;
259 }
260 }
261 }
262
263 /*
264 * Performs buffer right bits shift
265 */
266 function _rightShift(buffer) {
267 var carry, prevcarry;
268 for (var i = 0; i < buffer.length; i++) {
269 carry = prevcarry;
270 prevcarry = (buffer[i] & 0x1) !== 0;
271 buffer[i] >>= 1;
272 if (carry && i > 0) {
273 buffer[i] |= 0x80;
274 }
275 }
276 }
277
278}());
\No newline at end of file