475 lines
13 KiB
JavaScript
475 lines
13 KiB
JavaScript
|
|
// Use Uint8Array directly in the browser, use Buffer in Node.js but don't
|
||
|
|
// speak its name directly to avoid bundlers pulling in the `Buffer` polyfill
|
||
|
|
|
||
|
|
// @ts-ignore
|
||
|
|
export const useBuffer = globalThis.process &&
|
||
|
|
// @ts-ignore
|
||
|
|
!globalThis.process.browser &&
|
||
|
|
// @ts-ignore
|
||
|
|
globalThis.Buffer &&
|
||
|
|
// @ts-ignore
|
||
|
|
typeof globalThis.Buffer.isBuffer === 'function'
|
||
|
|
|
||
|
|
const textDecoder = new TextDecoder()
|
||
|
|
const textEncoder = new TextEncoder()
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} buf
|
||
|
|
* @returns {boolean}
|
||
|
|
*/
|
||
|
|
function isBuffer (buf) {
|
||
|
|
// @ts-ignore
|
||
|
|
return useBuffer && globalThis.Buffer.isBuffer(buf)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array|number[]} buf
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
export function asU8A (buf) {
|
||
|
|
/* c8 ignore next */
|
||
|
|
if (!(buf instanceof Uint8Array)) {
|
||
|
|
return Uint8Array.from(buf)
|
||
|
|
}
|
||
|
|
return isBuffer(buf) ? new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength) : buf
|
||
|
|
}
|
||
|
|
|
||
|
|
export const toString = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} bytes
|
||
|
|
* @param {number} start
|
||
|
|
* @param {number} end
|
||
|
|
*/
|
||
|
|
(bytes, start, end) => {
|
||
|
|
return end - start > 64
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
// @ts-ignore
|
||
|
|
globalThis.Buffer.from(bytes.subarray(start, end)).toString('utf8')
|
||
|
|
: utf8Slice(bytes, start, end)
|
||
|
|
}
|
||
|
|
/* c8 ignore next 11 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} bytes
|
||
|
|
* @param {number} start
|
||
|
|
* @param {number} end
|
||
|
|
*/
|
||
|
|
(bytes, start, end) => {
|
||
|
|
return end - start > 64
|
||
|
|
? textDecoder.decode(bytes.subarray(start, end))
|
||
|
|
: utf8Slice(bytes, start, end)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const fromString = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {string} string
|
||
|
|
*/
|
||
|
|
(string) => {
|
||
|
|
return string.length > 64
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
// @ts-ignore
|
||
|
|
globalThis.Buffer.from(string)
|
||
|
|
: utf8ToBytes(string)
|
||
|
|
}
|
||
|
|
/* c8 ignore next 7 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {string} string
|
||
|
|
*/
|
||
|
|
(string) => {
|
||
|
|
return string.length > 64 ? textEncoder.encode(string) : utf8ToBytes(string)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Buffer variant not fast enough for what we need
|
||
|
|
* @param {number[]} arr
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
export const fromArray = (arr) => {
|
||
|
|
return Uint8Array.from(arr)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const slice = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} bytes
|
||
|
|
* @param {number} start
|
||
|
|
* @param {number} end
|
||
|
|
*/
|
||
|
|
(bytes, start, end) => {
|
||
|
|
if (isBuffer(bytes)) {
|
||
|
|
return new Uint8Array(bytes.subarray(start, end))
|
||
|
|
}
|
||
|
|
return bytes.slice(start, end)
|
||
|
|
}
|
||
|
|
/* c8 ignore next 9 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} bytes
|
||
|
|
* @param {number} start
|
||
|
|
* @param {number} end
|
||
|
|
*/
|
||
|
|
(bytes, start, end) => {
|
||
|
|
return bytes.slice(start, end)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const concat = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array[]} chunks
|
||
|
|
* @param {number} length
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(chunks, length) => {
|
||
|
|
// might get a stray plain Array here
|
||
|
|
/* c8 ignore next 1 */
|
||
|
|
chunks = chunks.map((c) => c instanceof Uint8Array
|
||
|
|
? c
|
||
|
|
// this case is occasionally missed during test runs so becomes coverage-flaky
|
||
|
|
/* c8 ignore next 4 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
// @ts-ignore
|
||
|
|
globalThis.Buffer.from(c))
|
||
|
|
// @ts-ignore
|
||
|
|
return asU8A(globalThis.Buffer.concat(chunks, length))
|
||
|
|
}
|
||
|
|
/* c8 ignore next 19 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array[]} chunks
|
||
|
|
* @param {number} length
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(chunks, length) => {
|
||
|
|
const out = new Uint8Array(length)
|
||
|
|
let off = 0
|
||
|
|
for (let b of chunks) {
|
||
|
|
if (off + b.length > out.length) {
|
||
|
|
// final chunk that's bigger than we need
|
||
|
|
b = b.subarray(0, out.length - off)
|
||
|
|
}
|
||
|
|
out.set(b, off)
|
||
|
|
off += b.length
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
export const alloc = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {number} size
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(size) => {
|
||
|
|
// we always write over the contents we expose so this should be safe
|
||
|
|
// @ts-ignore
|
||
|
|
return globalThis.Buffer.allocUnsafe(size)
|
||
|
|
}
|
||
|
|
/* c8 ignore next 8 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {number} size
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(size) => {
|
||
|
|
return new Uint8Array(size)
|
||
|
|
}
|
||
|
|
|
||
|
|
export const toHex = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} d
|
||
|
|
* @returns {string}
|
||
|
|
*/
|
||
|
|
(d) => {
|
||
|
|
if (typeof d === 'string') {
|
||
|
|
return d
|
||
|
|
}
|
||
|
|
// @ts-ignore
|
||
|
|
return globalThis.Buffer.from(toBytes(d)).toString('hex')
|
||
|
|
}
|
||
|
|
/* c8 ignore next 12 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} d
|
||
|
|
* @returns {string}
|
||
|
|
*/
|
||
|
|
(d) => {
|
||
|
|
if (typeof d === 'string') {
|
||
|
|
return d
|
||
|
|
}
|
||
|
|
// @ts-ignore not smart enough to figure this out
|
||
|
|
return Array.prototype.reduce.call(toBytes(d), (p, c) => `${p}${c.toString(16).padStart(2, '0')}`, '')
|
||
|
|
}
|
||
|
|
|
||
|
|
export const fromHex = useBuffer
|
||
|
|
? // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {string|Uint8Array} hex
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(hex) => {
|
||
|
|
if (hex instanceof Uint8Array) {
|
||
|
|
return hex
|
||
|
|
}
|
||
|
|
// @ts-ignore
|
||
|
|
return globalThis.Buffer.from(hex, 'hex')
|
||
|
|
}
|
||
|
|
/* c8 ignore next 17 */
|
||
|
|
: // eslint-disable-line operator-linebreak
|
||
|
|
/**
|
||
|
|
* @param {string|Uint8Array} hex
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
(hex) => {
|
||
|
|
if (hex instanceof Uint8Array) {
|
||
|
|
return hex
|
||
|
|
}
|
||
|
|
if (!hex.length) {
|
||
|
|
return new Uint8Array(0)
|
||
|
|
}
|
||
|
|
return new Uint8Array(hex.split('')
|
||
|
|
.map((/** @type {string} */ c, /** @type {number} */ i, /** @type {string[]} */ d) => i % 2 === 0 ? `0x${c}${d[i + 1]}` : '')
|
||
|
|
.filter(Boolean)
|
||
|
|
.map((/** @type {string} */ e) => parseInt(e, 16)))
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array|ArrayBuffer|ArrayBufferView} obj
|
||
|
|
* @returns {Uint8Array}
|
||
|
|
*/
|
||
|
|
function toBytes (obj) {
|
||
|
|
if (obj instanceof Uint8Array && obj.constructor.name === 'Uint8Array') {
|
||
|
|
return obj
|
||
|
|
}
|
||
|
|
if (obj instanceof ArrayBuffer) {
|
||
|
|
return new Uint8Array(obj)
|
||
|
|
}
|
||
|
|
if (ArrayBuffer.isView(obj)) {
|
||
|
|
return new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength)
|
||
|
|
}
|
||
|
|
/* c8 ignore next */
|
||
|
|
throw new Error('Unknown type, must be binary type')
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} b1
|
||
|
|
* @param {Uint8Array} b2
|
||
|
|
* @returns {number}
|
||
|
|
*/
|
||
|
|
export function compare (b1, b2) {
|
||
|
|
/* c8 ignore next 5 */
|
||
|
|
if (isBuffer(b1) && isBuffer(b2)) {
|
||
|
|
// probably not possible to get here in the current API
|
||
|
|
// @ts-ignore Buffer
|
||
|
|
return b1.compare(b2)
|
||
|
|
}
|
||
|
|
for (let i = 0; i < b1.length; i++) {
|
||
|
|
if (b1[i] === b2[i]) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
return b1[i] < b2[i] ? -1 : 1
|
||
|
|
} /* c8 ignore next 3 */
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// The below code is mostly taken from https://github.com/feross/buffer
|
||
|
|
// Licensed MIT. Copyright (c) Feross Aboukhadijeh
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {string} string
|
||
|
|
* @param {number} [units]
|
||
|
|
* @returns {number[]}
|
||
|
|
*/
|
||
|
|
function utf8ToBytes (string, units = Infinity) {
|
||
|
|
let codePoint
|
||
|
|
const length = string.length
|
||
|
|
let leadSurrogate = null
|
||
|
|
const bytes = []
|
||
|
|
|
||
|
|
for (let i = 0; i < length; ++i) {
|
||
|
|
codePoint = string.charCodeAt(i)
|
||
|
|
|
||
|
|
// is surrogate component
|
||
|
|
if (codePoint > 0xd7ff && codePoint < 0xe000) {
|
||
|
|
// last char was a lead
|
||
|
|
if (!leadSurrogate) {
|
||
|
|
// no lead yet
|
||
|
|
/* c8 ignore next 9 */
|
||
|
|
if (codePoint > 0xdbff) {
|
||
|
|
// unexpected trail
|
||
|
|
if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd)
|
||
|
|
continue
|
||
|
|
} else if (i + 1 === length) {
|
||
|
|
// unpaired lead
|
||
|
|
if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
// valid lead
|
||
|
|
leadSurrogate = codePoint
|
||
|
|
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2 leads in a row
|
||
|
|
/* c8 ignore next 5 */
|
||
|
|
if (codePoint < 0xdc00) {
|
||
|
|
if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd)
|
||
|
|
leadSurrogate = codePoint
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
// valid surrogate pair
|
||
|
|
codePoint = (leadSurrogate - 0xd800 << 10 | codePoint - 0xdc00) + 0x10000
|
||
|
|
/* c8 ignore next 4 */
|
||
|
|
} else if (leadSurrogate) {
|
||
|
|
// valid bmp char, but last char was a lead
|
||
|
|
if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd)
|
||
|
|
}
|
||
|
|
|
||
|
|
leadSurrogate = null
|
||
|
|
|
||
|
|
// encode utf8
|
||
|
|
if (codePoint < 0x80) {
|
||
|
|
/* c8 ignore next 1 */
|
||
|
|
if ((units -= 1) < 0) break
|
||
|
|
bytes.push(codePoint)
|
||
|
|
} else if (codePoint < 0x800) {
|
||
|
|
/* c8 ignore next 1 */
|
||
|
|
if ((units -= 2) < 0) break
|
||
|
|
bytes.push(
|
||
|
|
codePoint >> 0x6 | 0xc0,
|
||
|
|
codePoint & 0x3f | 0x80
|
||
|
|
)
|
||
|
|
} else if (codePoint < 0x10000) {
|
||
|
|
/* c8 ignore next 1 */
|
||
|
|
if ((units -= 3) < 0) break
|
||
|
|
bytes.push(
|
||
|
|
codePoint >> 0xc | 0xe0,
|
||
|
|
codePoint >> 0x6 & 0x3f | 0x80,
|
||
|
|
codePoint & 0x3f | 0x80
|
||
|
|
)
|
||
|
|
/* c8 ignore next 9 */
|
||
|
|
} else if (codePoint < 0x110000) {
|
||
|
|
if ((units -= 4) < 0) break
|
||
|
|
bytes.push(
|
||
|
|
codePoint >> 0x12 | 0xf0,
|
||
|
|
codePoint >> 0xc & 0x3f | 0x80,
|
||
|
|
codePoint >> 0x6 & 0x3f | 0x80,
|
||
|
|
codePoint & 0x3f | 0x80
|
||
|
|
)
|
||
|
|
} else {
|
||
|
|
/* c8 ignore next 2 */
|
||
|
|
throw new Error('Invalid code point')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return bytes
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Uint8Array} buf
|
||
|
|
* @param {number} offset
|
||
|
|
* @param {number} end
|
||
|
|
* @returns {string}
|
||
|
|
*/
|
||
|
|
function utf8Slice (buf, offset, end) {
|
||
|
|
const res = []
|
||
|
|
|
||
|
|
while (offset < end) {
|
||
|
|
const firstByte = buf[offset]
|
||
|
|
let codePoint = null
|
||
|
|
let bytesPerSequence = (firstByte > 0xef) ? 4 : (firstByte > 0xdf) ? 3 : (firstByte > 0xbf) ? 2 : 1
|
||
|
|
|
||
|
|
if (offset + bytesPerSequence <= end) {
|
||
|
|
let secondByte, thirdByte, fourthByte, tempCodePoint
|
||
|
|
|
||
|
|
switch (bytesPerSequence) {
|
||
|
|
case 1:
|
||
|
|
if (firstByte < 0x80) {
|
||
|
|
codePoint = firstByte
|
||
|
|
}
|
||
|
|
break
|
||
|
|
case 2:
|
||
|
|
secondByte = buf[offset + 1]
|
||
|
|
if ((secondByte & 0xc0) === 0x80) {
|
||
|
|
tempCodePoint = (firstByte & 0x1f) << 0x6 | (secondByte & 0x3f)
|
||
|
|
if (tempCodePoint > 0x7f) {
|
||
|
|
codePoint = tempCodePoint
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break
|
||
|
|
case 3:
|
||
|
|
secondByte = buf[offset + 1]
|
||
|
|
thirdByte = buf[offset + 2]
|
||
|
|
if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80) {
|
||
|
|
tempCodePoint = (firstByte & 0xf) << 0xc | (secondByte & 0x3f) << 0x6 | (thirdByte & 0x3f)
|
||
|
|
/* c8 ignore next 3 */
|
||
|
|
if (tempCodePoint > 0x7ff && (tempCodePoint < 0xd800 || tempCodePoint > 0xdfff)) {
|
||
|
|
codePoint = tempCodePoint
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break
|
||
|
|
case 4:
|
||
|
|
secondByte = buf[offset + 1]
|
||
|
|
thirdByte = buf[offset + 2]
|
||
|
|
fourthByte = buf[offset + 3]
|
||
|
|
if ((secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80 && (fourthByte & 0xc0) === 0x80) {
|
||
|
|
tempCodePoint = (firstByte & 0xf) << 0x12 | (secondByte & 0x3f) << 0xc | (thirdByte & 0x3f) << 0x6 | (fourthByte & 0x3f)
|
||
|
|
if (tempCodePoint > 0xffff && tempCodePoint < 0x110000) {
|
||
|
|
codePoint = tempCodePoint
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* c8 ignore next 5 */
|
||
|
|
if (codePoint === null) {
|
||
|
|
// we did not generate a valid codePoint so insert a
|
||
|
|
// replacement char (U+FFFD) and advance only 1 byte
|
||
|
|
codePoint = 0xfffd
|
||
|
|
bytesPerSequence = 1
|
||
|
|
} else if (codePoint > 0xffff) {
|
||
|
|
// encode to utf16 (surrogate pair dance)
|
||
|
|
codePoint -= 0x10000
|
||
|
|
res.push(codePoint >>> 10 & 0x3ff | 0xd800)
|
||
|
|
codePoint = 0xdc00 | codePoint & 0x3ff
|
||
|
|
}
|
||
|
|
|
||
|
|
res.push(codePoint)
|
||
|
|
offset += bytesPerSequence
|
||
|
|
}
|
||
|
|
|
||
|
|
return decodeCodePointsArray(res)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Based on http://stackoverflow.com/a/22747272/680742, the browser with
|
||
|
|
// the lowest limit is Chrome, with 0x10000 args.
|
||
|
|
// We go 1 magnitude less, for safety
|
||
|
|
const MAX_ARGUMENTS_LENGTH = 0x1000
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {number[]} codePoints
|
||
|
|
* @returns {string}
|
||
|
|
*/
|
||
|
|
export function decodeCodePointsArray (codePoints) {
|
||
|
|
const len = codePoints.length
|
||
|
|
if (len <= MAX_ARGUMENTS_LENGTH) {
|
||
|
|
return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
|
||
|
|
}
|
||
|
|
/* c8 ignore next 10 */
|
||
|
|
// Decode in chunks to avoid "call stack size exceeded".
|
||
|
|
let res = ''
|
||
|
|
let i = 0
|
||
|
|
while (i < len) {
|
||
|
|
res += String.fromCharCode.apply(
|
||
|
|
String,
|
||
|
|
codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return res
|
||
|
|
}
|