'use strict' const {Buffer} = require('buffer') const NoFilter = require('nofilter') const stream = require('stream') const constants = require('./constants') const {NUMBYTES, SHIFT32, BI, SYMS} = constants const MAX_SAFE_HIGH = 0x1fffff /** * Convert a UTF8-encoded Buffer to a JS string. If possible, throw an error * on invalid UTF8. Byte Order Marks are not looked at or stripped. * * @private */ const td = new TextDecoder('utf8', {fatal: true, ignoreBOM: true}) exports.utf8 = buf => td.decode(buf) exports.utf8.checksUTF8 = true function isReadable(s) { // Is this a readable stream? In the webpack version, instanceof isn't // working correctly. if (s instanceof stream.Readable) { return true } return ['read', 'on', 'pipe'].every(f => typeof s[f] === 'function') } exports.isBufferish = function isBufferish(b) { return b && (typeof b === 'object') && ((Buffer.isBuffer(b)) || (b instanceof Uint8Array) || (b instanceof Uint8ClampedArray) || (b instanceof ArrayBuffer) || (b instanceof DataView)) } exports.bufferishToBuffer = function bufferishToBuffer(b) { if (Buffer.isBuffer(b)) { return b } else if (ArrayBuffer.isView(b)) { return Buffer.from(b.buffer, b.byteOffset, b.byteLength) } else if (b instanceof ArrayBuffer) { return Buffer.from(b) } return null } exports.parseCBORint = function parseCBORint(ai, buf) { switch (ai) { case NUMBYTES.ONE: return buf.readUInt8(0) case NUMBYTES.TWO: return buf.readUInt16BE(0) case NUMBYTES.FOUR: return buf.readUInt32BE(0) case NUMBYTES.EIGHT: { const f = buf.readUInt32BE(0) const g = buf.readUInt32BE(4) if (f > MAX_SAFE_HIGH) { return (BigInt(f) * BI.SHIFT32) + BigInt(g) } return (f * SHIFT32) + g } default: throw new Error(`Invalid additional info for int: ${ai}`) } } exports.writeHalf = function writeHalf(buf, half) { // Assume 0, -0, NaN, Infinity, and -Infinity have already been caught // HACK: everyone settle in. This isn't going to be pretty. // Translate cn-cbor's C code (from Carsten Borman): // uint32_t be32; // uint16_t be16, u16; // union { // float f; // uint32_t u; // } u32; // u32.f = float_val; const u32 = Buffer.allocUnsafe(4) u32.writeFloatBE(half, 0) const u = u32.readUInt32BE(0) // If ((u32.u & 0x1FFF) == 0) { /* worth trying half */ // hildjj: If the lower 13 bits aren't 0, // we will lose precision in the conversion. // mant32 = 24bits, mant16 = 11bits, 24-11 = 13 if ((u & 0x1FFF) !== 0) { return false } // Sign, exponent, mantissa // int s16 = (u32.u >> 16) & 0x8000; // int exp = (u32.u >> 23) & 0xff; // int mant = u32.u & 0x7fffff; let s16 = (u >> 16) & 0x8000 // Top bit is sign const exp = (u >> 23) & 0xff // Then 5 bits of exponent const mant = u & 0x7fffff // Hildjj: zeros already handled. Assert if you don't believe me. // if (exp == 0 && mant == 0) // ; /* 0.0, -0.0 */ // else if (exp >= 113 && exp <= 142) /* normalized */ // s16 += ((exp - 112) << 10) + (mant >> 13); if ((exp >= 113) && (exp <= 142)) { s16 += ((exp - 112) << 10) + (mant >> 13) } else if ((exp >= 103) && (exp < 113)) { // Denormalized numbers // else if (exp >= 103 && exp < 113) { /* denorm, exp16 = 0 */ // if (mant & ((1 << (126 - exp)) - 1)) // goto float32; /* loss of precision */ // s16 += ((mant + 0x800000) >> (126 - exp)); if (mant & ((1 << (126 - exp)) - 1)) { return false } s16 += ((mant + 0x800000) >> (126 - exp)) } else { // } else if (exp == 255 && mant == 0) { /* Inf */ // s16 += 0x7c00; // hildjj: Infinity already handled // } else // goto float32; /* loss of range */ return false } // Done // ensure_writable(3); // u16 = s16; // be16 = hton16p((const uint8_t*)&u16); buf.writeUInt16BE(s16) return true } exports.parseHalf = function parseHalf(buf) { const sign = buf[0] & 0x80 ? -1 : 1 const exp = (buf[0] & 0x7C) >> 2 const mant = ((buf[0] & 0x03) << 8) | buf[1] if (!exp) { return sign * 5.9604644775390625e-8 * mant } else if (exp === 0x1f) { return sign * (mant ? NaN : Infinity) } return sign * (2 ** (exp - 25)) * (1024 + mant) } exports.parseCBORfloat = function parseCBORfloat(buf) { switch (buf.length) { case 2: return exports.parseHalf(buf) case 4: return buf.readFloatBE(0) case 8: return buf.readDoubleBE(0) default: throw new Error(`Invalid float size: ${buf.length}`) } } exports.hex = function hex(s) { return Buffer.from(s.replace(/^0x/, ''), 'hex') } exports.bin = function bin(s) { s = s.replace(/\s/g, '') let start = 0 let end = (s.length % 8) || 8 const chunks = [] while (end <= s.length) { chunks.push(parseInt(s.slice(start, end), 2)) start = end end += 8 } return Buffer.from(chunks) } exports.arrayEqual = function arrayEqual(a, b) { if ((a == null) && (b == null)) { return true } if ((a == null) || (b == null)) { return false } return (a.length === b.length) && a.every((elem, i) => elem === b[i]) } exports.bufferToBigInt = function bufferToBigInt(buf) { return BigInt(`0x${buf.toString('hex')}`) } exports.cborValueToString = function cborValueToString(val, float_bytes = -1) { switch (typeof val) { case 'symbol': { switch (val) { case SYMS.NULL: return 'null' case SYMS.UNDEFINED: return 'undefined' case SYMS.BREAK: return 'BREAK' } // Impossible in node 10 /* istanbul ignore if */ if (val.description) { return val.description } // On node10, Symbol doesn't have description. Parse it out of the // toString value, which looks like `Symbol(foo)`. const s = val.toString() const m = s.match(/^Symbol\((?.*)\)/) /* istanbul ignore if */ if (m && m.groups.name) { // Impossible in node 12+ /* istanbul ignore next */ return m.groups.name } return 'Symbol' } case 'string': return JSON.stringify(val) case 'bigint': return val.toString() case 'number': { const s = Object.is(val, -0) ? '-0' : String(val) return (float_bytes > 0) ? `${s}_${float_bytes}` : s } case 'object': { if (!val) { return 'null' } const buf = exports.bufferishToBuffer(val) if (buf) { const hex = buf.toString('hex') return (float_bytes === -Infinity) ? hex : `h'${hex}'` } if (val && typeof val[Symbol.for('nodejs.util.inspect.custom')] === 'function') { return val[Symbol.for('nodejs.util.inspect.custom')]() } // Shouldn't get non-empty arrays here if (Array.isArray(val)) { return '[]' } // This should be all that is left return '{}' } } return String(val) } exports.guessEncoding = function guessEncoding(input, encoding) { if (typeof input === 'string') { return new NoFilter(input, (encoding == null) ? 'hex' : encoding) } const buf = exports.bufferishToBuffer(input) if (buf) { return new NoFilter(buf) } if (isReadable(input)) { return input } throw new Error('Unknown input type') } const B64URL_SWAPS = { '=': '', '+': '-', '/': '_', } /** * @param {Buffer|Uint8Array|Uint8ClampedArray|ArrayBuffer|DataView} buf * Buffer to convert. * @returns {string} Base64url string. * @private */ exports.base64url = function base64url(buf) { return exports.bufferishToBuffer(buf) .toString('base64') .replace(/[=+/]/g, c => B64URL_SWAPS[c]) } /** * @param {Buffer|Uint8Array|Uint8ClampedArray|ArrayBuffer|DataView} buf * Buffer to convert. * @returns {string} Base64 string. * @private */ exports.base64 = function base64(buf) { return exports.bufferishToBuffer(buf).toString('base64') } exports.isBigEndian = function isBigEndian() { const array = new Uint8Array(4) const view = new Uint32Array(array.buffer) return !((view[0] = 1) & array[0]) }