0xweb
Version:
Contract package manager and other web3 tools
197 lines (175 loc) • 6.52 kB
text/typescript
import { is_NODE } from 'atma-utils';
import { TEth } from '@dequanto/models/TEth';
interface IBufferUtils {
fromHex(hex: string | TEth.Hex): Uint8Array
toHex(buffer: Uint8Array): TEth.Hex
fromString(string: string, encoding?: BufferEncoding): Uint8Array
toString(buffer: Uint8Array, encoding?: BufferEncoding): string
fromBigInt(value: bigint): Uint8Array
toBigInt(buffer: Uint8Array): bigint
concat(buffers: Uint8Array[]): Uint8Array
ensure(mix: string | boolean | bigint | number | Uint8Array): Uint8Array
}
export type TBytes = Uint8Array;
abstract class BufferBase implements IBufferUtils {
abstract fromHex(hex: string | TEth.Hex): Uint8Array
abstract toHex(buffer: Uint8Array): TEth.Hex
abstract fromString(string: string, encoding?: BufferEncoding): Uint8Array
abstract toString(buffer: Uint8Array, encoding?: BufferEncoding): string
abstract concat(buffers: Uint8Array[]): Uint8Array
ensure (mix: string | boolean | bigint | number | Uint8Array) {
if (mix == null) {
return new Uint8Array(0);
}
if (typeof mix === 'string') {
let str = mix.startsWith('0x') ? mix.substring(2) : mix;
if (str.length % 2 !== 0) {
str = '0' + str;
}
return this.fromHex(str);
}
if (mix instanceof Uint8Array) {
return mix;
}
if (typeof mix === 'boolean') {
return new Uint8Array(mix ? [1] : [0]);
}
if (typeof mix === 'number') {
if (Math.floor(mix) !== mix) {
throw new Error('Floats are not supported for buffer array');
}
mix = BigInt(mix);
}
if (typeof mix === 'bigint') {
return $buffer.fromBigInt(mix);
}
console.error(mix);
throw new Error(`Unexpected buffer type: ${mix} (${typeof mix})`);
}
toBigInt(buffer: Uint8Array): bigint {
let result = 0n;
let length = buffer.length;
for (let i = 0; i < length; i++) {
result = (result << 8n) | BigInt(buffer[i]);
}
return result;
}
fromBigInt(value: bigint | number | string): Uint8Array {
if (typeof value === 'number' || typeof value === 'string') {
value = BigInt(value)
}
if (value < 0n) {
throw new Error(`Cannot convert negative ${value} to Uint8Array`);
}
if (value === 0n) {
return new Uint8Array([ 0 ]);
}
// Determine the number of bytes needed to represent the BigInt
let byteCount = 0;
let tempValue = value;
while (tempValue > 0n) {
byteCount++;
tempValue >>= 8n; // Right-shift by 8 bits to check the next byte
}
const uint8Array = new Uint8Array(byteCount);
// Fill the Uint8Array with the bytes from the BigInt
for (let i = byteCount - 1; i >= 0; i--) {
uint8Array[i] = Number(value & 0xFFn); // Extract the least significant byte
value >>= 8n; // Right-shift by 8 bits to get the next byte
}
return uint8Array;
}
}
class NodeBufferUtils extends BufferBase {
fromString(str: string, encoding?: BufferEncoding): Uint8Array {
return Buffer.from(str, encoding ?? 'utf8');
}
toString(buffer: Uint8Array, encoding: BufferEncoding = 'utf8'): string {
return Buffer.from(buffer).toString(encoding);
}
fromHex(hex: string): Uint8Array {
return Buffer.from(utils.normalizeHex(hex), 'hex');
}
toHex(buffer: Uint8Array | Buffer): TEth.Hex {
if (buffer instanceof Buffer) {
return (`0x` + buffer.toString('hex')) as TEth.Hex;
}
return (`0x` + (buffer as Uint8Array).reduce((hex, x) => {
return hex + x.toString(16).padStart(2, '0');
}, '')) as TEth.Hex;
}
concat(buffers: Uint8Array[]) {
return Buffer.concat(buffers);
}
}
const HEX_CHARS = "0123456789abcdef";
const HEX_DIGITS = {
0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15
};
class WebBufferUtils extends BufferBase {
fromString(string: string, encoding?: string): Uint8Array {
if (encoding != null && /utf\-?8/.test(encoding) === false) {
throw new Error(`Only UTF8 Encoding supported`);
}
return new TextEncoder().encode(string);
}
toString(buffer: Uint8Array, encoding?: BufferEncoding): string {
if (encoding != null && /utf\-?8/.test(encoding) === false) {
throw new Error(`Only UTF8 Encoding supported`);
}
return new TextDecoder().decode(buffer);
}
fromHex(hex: string | TEth.Hex): Uint8Array {
hex = utils.normalizeHex(hex);
let bytes = new Uint8Array(Math.floor(hex.length / 2));
let i = 0;
for (; i < bytes.length; i++) {
const a = HEX_DIGITS[hex[i * 2]];
const b = HEX_DIGITS[hex[i * 2 + 1]];
if (a == null || b == null) {
break;
}
bytes[i] = (a << 4) | b;
}
return i === bytes.length
? bytes
: bytes.slice(0, i);
}
toHex(buffer: Uint8Array): TEth.Hex {
let hex = '';
for (let i = 0; i < buffer.length; i++) {
let b = buffer[i];
hex += HEX_CHARS[b >> 4] + HEX_CHARS[b & 15];
}
return ('0x' + hex) as TEth.Hex;
}
concat(buffers: Uint8Array[]) {
let size = buffers.reduce((a, x) => a + x.length, 0);
let buffer = new Uint8Array(size);
let offset = 0;
for (let i = 0; i < buffers.length; i++) {
let buf = buffers[i];
buffer.set(buf, offset);
offset += buf.length;
}
return buffer;
}
}
namespace utils {
export function normalizeHex(hex: string) {
if (hex.startsWith('0x')) {
hex = hex.substring(2);
}
if (hex.length % 2 !== 0) {
throw new Error(`Not valid hex buffer. Char count not even: ${hex}`);
}
if (hex.length > 0 && /^[\da-f]+$/i.test(hex) === false) {
throw new Error(`Not valid hex buffer. Invalid char in ${hex}`);
}
return hex;
}
}
export const $buffer: IBufferUtils = is_NODE
? new NodeBufferUtils()
: new WebBufferUtils()
;