buffer-small.ts•24.5 kB
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
export interface BufferClass {
readBigInt64BE(offset: number): bigint
readBigInt64BE(offset: number): bigint
readBigInt64LE(offset: number): bigint
readBigInt64LE(offset: number): bigint
readBigUint64BE(offset: number): bigint
readBigUInt64BE(offset: number): bigint
readBigUint64LE(offset: number): bigint
readBigUInt64LE(offset: number): bigint
readDoubleBE(offset: number): number
readDoubleLE(offset: number): number
readFloatBE(offset: number): number
readFloatLE(offset: number): number
readInt16BE(offset: number): number
readInt16LE(offset: number): number
readInt32BE(offset: number): number
readInt32LE(offset: number): number
readInt8(offset: number): number
readIntBE(offset: number, byteLength: number): number
readIntLE(offset: number, byteLength: number): number
readUint16BE(offset: number): number
readUInt16BE(offset: number): number
readUint16LE(offset: number): number
readUInt16LE(offset: number): number
readUint32BE(offset: number): number
readUInt32BE(offset: number): number
readUint32LE(offset: number): number
readUInt32LE(offset: number): number
readUint8(offset: number): number
readUInt8(offset: number): number
readUInt8(offset: number): number
readUintBE(offset: number, byteLength: number): number
readUIntBE(offset: number, byteLength: number): number
readUintLE(offset: number, byteLength: number): number
readUIntLE(offset: number, byteLength: number): number
writeBigInt64BE(value: bigint, offset: number): number
writeBigInt64BE(value: bigint, offset: number): number
writeBigInt64LE(value: bigint, offset: number): number
writeBigInt64LE(value: bigint, offset: number): number
writeBigUint64BE(value: bigint, offset: number): number
writeBigUInt64BE(value: bigint, offset: number): number
writeBigUint64LE(value: bigint, offset: number): number
writeBigUInt64LE(value: bigint, offset: number): number
writeDoubleBE(value: number, offset: number): number
writeDoubleLE(value: number, offset: number): number
writeFloatBE(value: number, offset: number): number
writeFloatLE(value: number, offset: number): number
writeInt16BE(value: number, offset: number): number
writeInt16LE(value: number, offset: number): number
writeInt32BE(value: number, offset: number): number
writeInt32LE(value: number, offset: number): number
writeInt8(value: number, offset: number): number
writeIntBE(value: number, offset: number, byteLength: number): number
writeIntLE(value: number, offset: number, byteLength: number): number
writeUint16BE(value: number, offset: number): number
writeUInt16BE(value: number, offset: number): number
writeUint16LE(value: number, offset: number): number
writeUInt16LE(value: number, offset: number): number
writeUint32BE(value: number, offset: number): number
writeUInt32BE(value: number, offset: number): number
writeUint32LE(value: number, offset: number): number
writeUInt32LE(value: number, offset: number): number
writeUint8(value: number, offset: number): number
writeUInt8(value: number, offset: number): number
writeUintBE(value: number, offset: number, byteLength: number): number
writeUIntBE(value: number, offset: number, byteLength: number): number
writeUintLE(value: number, offset: number, byteLength: number): number
writeUIntLE(value: number, offset: number, byteLength: number): number
}
export class BufferClass extends Uint8Array /* implements NodeBuffer */ {
readonly _isBuffer = true
get offset() {
return this.byteOffset
}
static alloc(size: number, fill: string | number | Uint8Array = 0, encoding: Encoding = 'utf8') {
assertString(encoding, 'encoding')
return BufferClass.allocUnsafe(size).fill(fill, encoding)
}
static allocUnsafe(size: number) {
return BufferClass.from(size)
}
static allocUnsafeSlow(size: number) {
return BufferClass.from(size)
}
static isBuffer(arg: any): arg is BufferClass {
return arg && !!arg._isBuffer
}
static byteLength(string: unknown, encoding: Encoding = 'utf8') {
if (typeof string === 'string') return stringToBuffer(string, encoding).byteLength
if (string && string['byteLength']) return string['byteLength']
const e = new TypeError('The "string" argument must be of type string or an instance of Buffer or ArrayBuffer.')
e['code'] = 'ERR_INVALID_ARG_TYPE'
throw e
}
static isEncoding(encoding: string): encoding is Encoding {
return Encodings.includes(encoding as any)
}
static compare(buff1: Uint8Array, buff2: Uint8Array) {
assertUint8Array(buff1, 'buff1')
assertUint8Array(buff2, 'buff2')
for (let i = 0; i < buff1.length; i++) {
if (buff1[i] < buff2[i]) return -1
if (buff1[i] > buff2[i]) return 1
}
return buff1.length === buff2.length ? 0 : buff1.length > buff2.length ? 1 : -1
}
static from(value: unknown, encoding: any = 'utf8'): BufferClass {
if (value && typeof value === 'object' && value['type'] === 'Buffer') {
return new BufferClass(value['data'])
}
if (typeof value === 'number') {
return new BufferClass(new Uint8Array(value))
}
if (typeof value === 'string') {
return stringToBuffer(value, encoding)
}
if (ArrayBuffer.isView(value)) {
const { byteOffset, byteLength, buffer } = value
if ('map' in value && typeof value.map === 'function') {
return new BufferClass(
value.map((v) => v % 256),
byteOffset,
byteLength,
)
}
return new BufferClass(buffer, byteOffset, byteLength)
}
if (value && typeof value === 'object' && ('length' in value || 'byteLength' in value || 'buffer' in value)) {
return new BufferClass(value as ArrayLike<number>)
}
throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
}
static concat(list: readonly Uint8Array[], totalLength?: number) {
if (list.length === 0) return BufferClass.alloc(0)
const concat = ([] as number[]).concat(...list.map((item) => [...item]))
const result = BufferClass.alloc(totalLength !== undefined ? totalLength : concat.length)
result.set(totalLength !== undefined ? concat.slice(0, totalLength) : concat)
return result
}
slice(start = 0, end = this.length) {
return this.subarray(start, end)
}
subarray(start = 0, end = this.length) {
return Object.setPrototypeOf(super.subarray(start, end), BufferClass.prototype) as BufferClass
}
reverse() {
super.reverse()
return this
}
readIntBE(offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
let val = 0
for (let i = 0; i < byteLength; i++) {
val = val * 0x100 + view.getUint8(i)
}
if (view.getUint8(0) & 0x80) {
val -= Math.pow(0x100, byteLength)
}
return val
}
readIntLE(offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
let val = 0
for (let i = 0; i < byteLength; i++) {
val += view.getUint8(i) * Math.pow(0x100, i)
}
if (view.getUint8(byteLength - 1) & 0x80) {
val -= Math.pow(0x100, byteLength)
}
return val
}
readUIntBE(offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
let val = 0
for (let i = 0; i < byteLength; i++) {
val = val * 0x100 + view.getUint8(i)
}
return val
}
readUintBE(offset: number, byteLength: number) {
return this.readUIntBE(offset, byteLength)
}
readUIntLE(offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
let val = 0
for (let i = 0; i < byteLength; i++) {
val += view.getUint8(i) * Math.pow(0x100, i)
}
return val
}
readUintLE(offset: number, byteLength: number) {
return this.readUIntLE(offset, byteLength)
}
writeIntBE(value: number, offset: number, byteLength: number) {
value = value < 0 ? value + Math.pow(0x100, byteLength) : value
return this.writeUIntBE(value, offset, byteLength)
}
writeIntLE(value: number, offset: number, byteLength: number) {
value = value < 0 ? value + Math.pow(0x100, byteLength) : value
return this.writeUIntLE(value, offset, byteLength)
}
writeUIntBE(value: number, offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
for (let i = byteLength - 1; i >= 0; i--) {
view.setUint8(i, value & 0xff)
value = value / 0x100
}
return offset + byteLength
}
writeUintBE(value: number, offset: number, byteLength: number) {
return this.writeUIntBE(value, offset, byteLength)
}
writeUIntLE(value: number, offset: number, byteLength: number) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertNumber(byteLength, 'byteLength')
assertInteger(byteLength, 'byteLength')
const view = new DataView(this.buffer, offset, byteLength)
for (let i = 0; i < byteLength; i++) {
view.setUint8(i, value & 0xff) // bitwise 0xff is to get the last 8 bits
value = value / 0x100 // shift 8 bits to the right to iterate
}
return offset + byteLength
}
writeUintLE(value: number, offset: number, byteLength: number) {
return this.writeUIntLE(value, offset, byteLength)
}
toJSON() {
return { type: 'Buffer', data: Array.from(this) } as const
}
swap16() {
const buffer = new DataView(this.buffer, this.byteOffset, this.byteLength)
for (let i = 0; i < this.length; i += 2) {
buffer.setUint16(i, buffer.getUint16(i, true), false)
}
return this
}
swap32() {
const buffer = new DataView(this.buffer, this.byteOffset, this.byteLength)
for (let i = 0; i < this.length; i += 4) {
buffer.setUint32(i, buffer.getUint32(i, true), false)
}
return this
}
swap64() {
const view = new DataView(this.buffer, this.byteOffset, this.byteLength)
for (let i = 0; i < this.length; i += 8) {
view.setBigUint64(i, view.getBigUint64(i, true), false)
}
return this
}
compare(target: Uint8Array, targetStart = 0, targetEnd = target.length, sourceStart = 0, sourceEnd = this.length) {
assertUint8Array(target, 'target')
assertNumber(targetStart, 'targetStart')
assertNumber(targetEnd, 'targetEnd')
assertNumber(sourceStart, 'sourceStart')
assertNumber(sourceEnd, 'sourceEnd')
assertUnsigned(targetStart, 'targetStart')
assertUnsigned(targetEnd, 'targetEnd', target.length)
assertUnsigned(sourceStart, 'sourceStart')
assertUnsigned(sourceEnd, 'sourceEnd', this.length)
return BufferClass.compare(this.slice(sourceStart, sourceEnd), target.slice(targetStart, targetEnd))
}
equals(otherBuffer: Uint8Array) {
assertUint8Array(otherBuffer, 'otherBuffer')
return this.length === otherBuffer.length && this.every((v, i) => v === otherBuffer[i])
}
copy(target: Uint8Array, targetStart = 0, sourceStart = 0, sourceEnd = this.length) {
assertUnsigned(targetStart, 'targetStart')
assertUnsigned(sourceStart, 'sourceStart', this.length)
assertUnsigned(sourceEnd, 'sourceEnd')
targetStart >>>= 0
sourceStart >>>= 0
sourceEnd >>>= 0
let copiedBytes = 0
while (sourceStart < sourceEnd) {
if (this[sourceStart] === undefined) break
if (target[targetStart] === undefined) break
target[targetStart] = this[sourceStart]
copiedBytes++
sourceStart++
targetStart++
}
return copiedBytes
}
write(string: string, encoding?: Encoding): number
write(string: string, offset: number, encoding?: Encoding): number
write(string: string, offset: number, length: number, encoding?: Encoding): number
write(string: string, offsetEnc?: number | Encoding, lengthEnc?: number | Encoding, encoding: Encoding = 'utf8') {
const offset = typeof offsetEnc === 'string' ? 0 : (offsetEnc ?? 0)
let length = typeof lengthEnc === 'string' ? this.length - offset : (lengthEnc ?? this.length - offset)
encoding = typeof offsetEnc === 'string' ? offsetEnc : typeof lengthEnc === 'string' ? lengthEnc : encoding
assertNumber(offset, 'offset')
assertNumber(length, 'length')
assertUnsigned(offset, 'offset', this.length)
assertUnsigned(length, 'length', this.length)
if (encoding === 'ucs2' || encoding === 'ucs-2' || encoding === 'utf16le' || encoding === 'utf-16le') {
length = length - (length % 2)
}
return stringToBuffer(string, encoding).copy(this, offset, 0, length)
}
fill(
value: string | number | Uint8Array = 0,
offsetEnc: number | Encoding = 0,
endEnc: number | Encoding = this.length,
encoding: Encoding = 'utf-8',
) {
const offset = typeof offsetEnc === 'string' ? 0 : offsetEnc
const end = typeof endEnc === 'string' ? this.length : endEnc
encoding = typeof offsetEnc === 'string' ? offsetEnc : typeof endEnc === 'string' ? endEnc : encoding
value = BufferClass.from(typeof value === 'number' ? [value] : (value ?? []), encoding)
assertString(encoding, 'encoding')
assertUnsigned(offset, 'offset', this.length)
assertUnsigned(end, 'end', this.length)
if (value.length !== 0) {
for (let i = offset; i < end; i += value.length) {
super.set(value.slice(0, value.length + i >= this.length ? this.length - i : value.length), i)
}
}
return this
}
includes(value: string | number | Uint8Array, byteOffset: number | null = null, encoding: Encoding = 'utf-8') {
return this.indexOf(value, byteOffset, encoding) !== -1
}
lastIndexOf(
value: string | number | Uint8Array,
byteOffsetOrEncoding: number | Encoding | null = null,
encoding: Encoding = 'utf-8',
) {
return this.indexOf(value, byteOffsetOrEncoding, encoding, true)
}
indexOf(
value: string | number | Uint8Array,
byteOffsetOrEncoding: number | Encoding | null = null,
encoding: Encoding = 'utf-8',
lastIndexOf = false,
) {
const method = lastIndexOf ? this.findLastIndex.bind(this) : this.findIndex.bind(this)
encoding = typeof byteOffsetOrEncoding === 'string' ? byteOffsetOrEncoding : encoding
const toSearch = BufferClass.from(typeof value === 'number' ? [value] : value, encoding)
let byteOffset = typeof byteOffsetOrEncoding === 'string' ? 0 : byteOffsetOrEncoding
byteOffset = typeof byteOffsetOrEncoding === 'number' ? byteOffset : null
byteOffset = Number.isNaN(byteOffset) ? null : byteOffset
byteOffset ??= lastIndexOf ? this.length : 0
byteOffset = byteOffset < 0 ? this.length + byteOffset : byteOffset
if (toSearch.length === 0 && lastIndexOf === false) {
return byteOffset >= this.length ? this.length : byteOffset
}
if (toSearch.length === 0 && lastIndexOf === true) {
return (byteOffset >= this.length ? this.length : byteOffset) || this.length
}
return method((_, i) => {
const searchIf = lastIndexOf ? i <= byteOffset! : i >= byteOffset!
return searchIf && this[i] === toSearch[0] && toSearch.every((val, j) => this[i + j] === val)
})
}
toString(encoding: Encoding = 'utf8', start = 0, end = this.length) {
start = start < 0 ? 0 : start
encoding = encoding.toString().toLowerCase() as Encoding
if (end <= 0) return ''
if (encoding === 'utf8' || encoding === 'utf-8') {
return decoder.decode(this.slice(start, end))
}
if (encoding === 'base64' || encoding === 'base64url') {
const string = btoa(this.reduce((s, v) => s + c2s(v), ''))
if (encoding === 'base64url') {
return string.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
return string
}
if (encoding === 'binary' || encoding === 'ascii' || encoding === 'latin1' || encoding === 'latin-1') {
return this.slice(start, end).reduce((s, v) => s + c2s(v & (encoding === 'ascii' ? 0x7f : 0xff)), '')
}
if (encoding === 'ucs2' || encoding === 'ucs-2' || encoding === 'utf16le' || encoding === 'utf-16le') {
const view = new DataView(this.buffer.slice(start, end))
return Array.from({ length: view.byteLength / 2 }, (_, i) => {
// if the byte length is odd, the last character will be ignored
return i * 2 + 1 < view.byteLength ? c2s(view.getUint16(i * 2, true)) : ''
}).join('')
}
if (encoding === 'hex') {
return this.slice(start, end).reduce((s, v) => s + v.toString(16).padStart(2, '0'), '')
}
bufferPolyfillDoesNotImplement(`encoding "${encoding}"`)
}
toLocaleString() {
return this.toString()
}
inspect() {
const hex = this.toString('hex')
const byteHex = hex.match(/.{1,2}/g)!.join(' ')
return `<Buffer ${byteHex}>`
}
}
function stringToBuffer(value: string, encoding: string) {
encoding = encoding.toLowerCase() as Encoding
if (encoding === 'utf8' || encoding === 'utf-8') {
return new BufferClass(encoder.encode(value))
}
if (encoding === 'base64' || encoding === 'base64url') {
value = value.replace(/-/g, '+').replace(/_/g, '/')
value = value.replace(/[^A-Za-z0-9+/]/g, '')
return new BufferClass([...atob(value)].map((v) => v.charCodeAt(0)))
}
if (encoding === 'binary' || encoding === 'ascii' || encoding === 'latin1' || encoding === 'latin-1') {
return new BufferClass([...value].map((v) => v.charCodeAt(0)))
}
if (encoding === 'ucs2' || encoding === 'ucs-2' || encoding === 'utf16le' || encoding === 'utf-16le') {
const buffer = new BufferClass(value.length * 2)
const view = new DataView(buffer.buffer)
for (let i = 0; i < value.length; i++) {
view.setUint16(i * 2, value.charCodeAt(i), true)
}
return buffer
}
if (encoding === 'hex') {
const bytes = new BufferClass(value.length / 2)
for (let byteIndex = 0, i = 0; i < value.length; i += 2, byteIndex++) {
bytes[byteIndex] = parseInt(value.slice(i, i + 2), 16)
}
return bytes
}
bufferPolyfillDoesNotImplement(`encoding "${encoding}"`)
}
function initReadMethods(prototype: BufferClass) {
const dataViewProtoProps = Object.getOwnPropertyNames(DataView.prototype)
const dataViewMethods = dataViewProtoProps.filter((m) => m.startsWith('get') || m.startsWith('set'))
const bufferBaseMethods = dataViewMethods.map((m) => m.replace('get', 'read').replace('set', 'write'))
const genericReadMethod = (i: number, littleEndian: boolean) => {
return function (this: BufferClass, offset = 0) {
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
return new DataView(this.buffer)[dataViewMethods[i]](offset, littleEndian)
}
}
const genericWriteMethod = (i: number, littleEndian: boolean) => {
return function (this: BufferClass, value: any, offset = 0) {
const boundKey = dataViewMethods[i].match(/set(\w+\d+)/)![1].toLowerCase()
const bound = bounds[boundKey as keyof typeof bounds]
assertNumber(offset, 'offset')
assertInteger(offset, 'offset')
assertUnsigned(offset, 'offset', this.length - 1)
assertBounds(value, 'value', bound[0], bound[1])
new DataView(this.buffer)[dataViewMethods[i]](offset, value, littleEndian)
return offset + parseInt(dataViewMethods[i].match(/\d+/)![0]) / 8
}
}
const createAlias = (methods: string[]) => {
methods.forEach((method) => {
if (method.includes('Uint')) prototype[method.replace('Uint', 'UInt')] = prototype[method]
if (method.includes('Float64')) prototype[method.replace('Float64', 'Double')] = prototype[method]
if (method.includes('Float32')) prototype[method.replace('Float32', 'Float')] = prototype[method]
})
}
bufferBaseMethods.forEach((method, i) => {
if (method.startsWith('read')) {
prototype[method] = genericReadMethod(i, false)
prototype[method + 'LE'] = genericReadMethod(i, true)
prototype[method + 'BE'] = genericReadMethod(i, false)
}
if (method.startsWith('write')) {
prototype[method] = genericWriteMethod(i, false)
prototype[method + 'LE'] = genericWriteMethod(i, true)
prototype[method + 'BE'] = genericWriteMethod(i, false)
}
createAlias([method, method + 'LE', method + 'BE'])
})
}
function bufferPolyfillDoesNotImplement(message: string): never {
throw new Error(`Buffer polyfill does not implement "${message}"`)
}
function assertUint8Array(value: any, argName: string): asserts value is Uint8Array {
if (!(value instanceof Uint8Array)) {
throw new TypeError(`The "${argName}" argument must be an instance of Buffer or Uint8Array`)
}
}
function assertUnsigned(value: number, argName: string, maxValue = MAX_UNSIGNED_32_BIT + 1) {
if (value < 0 || value > maxValue) {
const e = new RangeError(
`The value of "${argName}" is out of range. It must be >= 0 && <= ${maxValue}. Received ${value}`,
)
e['code'] = 'ERR_OUT_OF_RANGE'
throw e
}
}
function assertNumber(value: any, argName: string): asserts value is number {
if (typeof value !== 'number') {
const e = new TypeError(`The "${argName}" argument must be of type number. Received type ${typeof value}.`)
e['code'] = 'ERR_INVALID_ARG_TYPE'
throw e
}
}
function assertInteger(value: any, argName: string): asserts value is number {
if (!Number.isInteger(value) || Number.isNaN(value)) {
const e = new RangeError(`The value of "${argName}" is out of range. It must be an integer. Received ${value}`)
e['code'] = 'ERR_OUT_OF_RANGE'
throw e
}
}
function assertBounds(value: number, argName: string, min: any, max: any) {
if (value < min || value > max) {
const e = new RangeError(
`The value of "${argName}" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
)
e['code'] = 'ERR_OUT_OF_RANGE'
throw e
}
}
function assertString(value: any, argName: string): asserts value is string {
if (typeof value !== 'string') {
const e = new TypeError(`The "${argName}" argument must be of type string. Received type ${typeof value}`)
e['code'] = 'ERR_INVALID_ARG_TYPE'
throw e
}
}
const bounds = {
int8: [-0x80, 0x7f],
int16: [-0x8000, 0x7fff],
int32: [-0x80000000, 0x7fffffff],
uint8: [0, 0xff],
uint16: [0, 0xffff],
uint32: [0, 0xffffffff],
float32: [-Infinity, Infinity],
float64: [-Infinity, Infinity],
bigint64: [-0x8000000000000000n, 0x7fffffffffffffffn],
biguint64: [0n, 0xffffffffffffffffn],
} as const
const encoder = new TextEncoder()
const decoder = new TextDecoder()
export type Encoding = (typeof Encodings)[number]
const Encodings = [
'utf8',
'utf-8',
'hex',
'base64',
'ascii',
'binary',
'base64url',
'ucs2',
'ucs-2',
'utf16le',
'utf-16le',
'latin1',
'latin-1',
] as const
const MAX_UNSIGNED_32_BIT = 0xffffffff
initReadMethods(BufferClass.prototype)
function $Buffer(value: unknown, encoding: any = 'utf8') {
return BufferClass.from(value, encoding)
}
export const Buffer = new Proxy($Buffer, {
construct(_, [value, encoding]) {
return BufferClass.from(value, encoding)
},
get(_, prop) {
return BufferClass[prop]
},
}) as typeof $Buffer & typeof BufferClass
const c2s = String.fromCodePoint
export default Buffer