import window from 'global/window'; // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2)); const repeat = function(str, len) { let acc = ''; while (len--) { acc += str; } return acc; }; // count the number of bits it would take to represent a number // we used to do this with log2 but BigInt does not support builtin math // Math.ceil(log2(x)); export const countBits = (x) => x.toString(2).length; // count the number of whole bytes it would take to represent a number export const countBytes = (x) => Math.ceil(countBits(x) / 8); export const padStart = (b, len, str = ' ') => (repeat(str, len) + b.toString()).slice(-len); export const isArrayBufferView = (obj) => { if (ArrayBuffer.isView === 'function') { return ArrayBuffer.isView(obj); } return obj && obj.buffer instanceof ArrayBuffer; }; export const isTypedArray = (obj) => isArrayBufferView(obj); export const toUint8 = function(bytes) { if (bytes instanceof Uint8Array) { return bytes; } if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) { // any non-number or NaN leads to empty uint8array // eslint-disable-next-line if (typeof bytes !== 'number' || (typeof bytes === 'number' && bytes !== bytes)) { bytes = 0; } else { bytes = [bytes]; } } return new Uint8Array( bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0 ); }; export const toHexString = function(bytes) { bytes = toUint8(bytes); let str = ''; for (let i = 0; i < bytes.length; i++) { str += padStart(bytes[i].toString(16), 2, '0'); } return str; }; export const toBinaryString = function(bytes) { bytes = toUint8(bytes); let str = ''; for (let i = 0; i < bytes.length; i++) { str += padStart(bytes[i].toString(2), 8, '0'); } return str; }; const BigInt = window.BigInt || Number; const BYTE_TABLE = [ BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000') ]; export const ENDIANNESS = (function() { const a = new Uint16Array([0xFFCC]); const b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength); if (b[0] === 0xFF) { return 'big'; } if (b[0] === 0xCC) { return 'little'; } return 'unknown'; })(); export const IS_BIG_ENDIAN = ENDIANNESS === 'big'; export const IS_LITTLE_ENDIAN = ENDIANNESS === 'little'; export const bytesToNumber = function(bytes, {signed = false, le = false} = {}) { bytes = toUint8(bytes); const fn = le ? 'reduce' : 'reduceRight'; const obj = bytes[fn] ? bytes[fn] : Array.prototype[fn]; let number = obj.call(bytes, function(total, byte, i) { const exponent = le ? i : Math.abs(i + 1 - bytes.length); return total + (BigInt(byte) * BYTE_TABLE[exponent]); }, BigInt(0)); if (signed) { const max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1); number = BigInt(number); if (number > max) { number -= max; number -= max; number -= BigInt(2); } } return Number(number); }; export const numberToBytes = function(number, {le = false} = {}) { // eslint-disable-next-line if ((typeof number !== 'bigint' && typeof number !== 'number') || (typeof number === 'number' && number !== number)) { number = 0; } number = BigInt(number); const byteCount = countBytes(number); const bytes = new Uint8Array(new ArrayBuffer(byteCount)); for (let i = 0; i < byteCount; i++) { const byteIndex = le ? i : Math.abs(i + 1 - bytes.length); bytes[byteIndex] = Number((number / BYTE_TABLE[i]) & BigInt(0xFF)); if (number < 0) { bytes[byteIndex] = Math.abs(~bytes[byteIndex]); bytes[byteIndex] -= i === 0 ? 1 : 2; } } return bytes; }; export const bytesToString = (bytes) => { if (!bytes) { return ''; } // TODO: should toUint8 handle cases where we only have 8 bytes // but report more since this is a Uint16+ Array? bytes = Array.prototype.slice.call(bytes); const string = String.fromCharCode.apply(null, toUint8(bytes)); try { return decodeURIComponent(escape(string)); } catch (e) { // if decodeURIComponent/escape fails, we are dealing with partial // or full non string data. Just return the potentially garbled string. } return string; }; export const stringToBytes = (string, stringIsBytes) => { if (typeof string !== 'string' && string && typeof string.toString === 'function') { string = string.toString(); } if (typeof string !== 'string') { return new Uint8Array(); } // If the string already is bytes, we don't have to do this // otherwise we do this so that we split multi length characters // into individual bytes if (!stringIsBytes) { string = unescape(encodeURIComponent(string)); } const view = new Uint8Array(string.length); for (let i = 0; i < string.length; i++) { view[i] = string.charCodeAt(i); } return view; }; export const concatTypedArrays = (...buffers) => { buffers = buffers.filter((b) => b && (b.byteLength || b.length) && typeof b !== 'string'); if (buffers.length <= 1) { // for 0 length we will return empty uint8 // for 1 length we return the first uint8 return toUint8(buffers[0]); } const totalLen = buffers.reduce((total, buf, i) => total + (buf.byteLength || buf.length), 0); const tempBuffer = new Uint8Array(totalLen); let offset = 0; buffers.forEach(function(buf) { buf = toUint8(buf); tempBuffer.set(buf, offset); offset += buf.byteLength; }); return tempBuffer; }; /** * Check if the bytes "b" are contained within bytes "a". * * @param {Uint8Array|Array} a * Bytes to check in * * @param {Uint8Array|Array} b * Bytes to check for * * @param {Object} options * options * * @param {Array|Uint8Array} [offset=0] * offset to use when looking at bytes in a * * @param {Array|Uint8Array} [mask=[]] * mask to use on bytes before comparison. * * @return {boolean} * If all bytes in b are inside of a, taking into account * bit masks. */ export const bytesMatch = (a, b, {offset = 0, mask = []} = {}) => { a = toUint8(a); b = toUint8(b); // ie 11 does not support uint8 every const fn = b.every ? b.every : Array.prototype.every; return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8 fn.call(b, (bByte, i) => { const aByte = (mask[i] ? (mask[i] & a[offset + i]) : a[offset + i]); return bByte === aByte; }); }; export const sliceBytes = function(src, start, end) { if (Uint8Array.prototype.slice) { return Uint8Array.prototype.slice.call(src, start, end); } return new Uint8Array(Array.prototype.slice.call(src, start, end)); }; export const reverseBytes = function(src) { if (src.reverse) { return src.reverse(); } return Array.prototype.reverse.call(src); };