ox
Version:
368 lines (322 loc) • 10.7 kB
text/typescript
import * as Bytes from './Bytes.js'
import * as Errors from './Errors.js'
import * as Hex from './Hex.js'
import * as Cursor from './internal/cursor.js'
import type { ExactPartial, RecursiveArray } from './internal/types.js'
/**
* Decodes a Recursive-Length Prefix (RLP) value into a {@link ox#Bytes.Bytes} value.
*
* @example
* ```ts twoslash
* import { Rlp } from 'ox'
* Rlp.toBytes('0x8b68656c6c6f20776f726c64')
* // Uint8Array([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
* ```
*
* @param value - The value to decode.
* @returns The decoded {@link ox#Bytes.Bytes} value.
*/
export function toBytes(
value: Bytes.Bytes | Hex.Hex,
): RecursiveArray<Bytes.Bytes> {
return to(value, 'Bytes')
}
export declare namespace toBytes {
type ErrorType = to.ErrorType
}
/**
* Decodes a Recursive-Length Prefix (RLP) value into a {@link ox#Hex.Hex} value.
*
* @example
* ```ts twoslash
* import { Rlp } from 'ox'
* Rlp.toHex('0x8b68656c6c6f20776f726c64')
* // 0x68656c6c6f20776f726c64
* ```
*
* @param value - The value to decode.
* @returns The decoded {@link ox#Hex.Hex} value.
*/
export function toHex(value: Bytes.Bytes | Hex.Hex): RecursiveArray<Hex.Hex> {
return to(value, 'Hex')
}
export declare namespace toHex {
type ErrorType = to.ErrorType
}
/////////////////////////////////////////////////////////////////////////////////
// Internal
/////////////////////////////////////////////////////////////////////////////////
/** @internal */
export function to<
value extends Bytes.Bytes | Hex.Hex,
to extends 'Hex' | 'Bytes',
>(value: value, to: to | 'Hex' | 'Bytes'): to.ReturnType<to> {
const to_ = to ?? (typeof value === 'string' ? 'Hex' : 'Bytes')
const bytes = (() => {
if (typeof value === 'string') {
if (value.length > 3 && value.length % 2 !== 0)
throw new Hex.InvalidLengthError(value)
return Bytes.fromHex(value)
}
return value as Bytes.Bytes
})()
const cursor = Cursor.create(bytes, {
recursiveReadLimit: Number.POSITIVE_INFINITY,
})
const result = decodeRlpCursor(cursor, to_)
return result as to.ReturnType<to>
}
/** @internal */
export declare namespace to {
type ReturnType<to extends 'Hex' | 'Bytes' = 'Hex' | 'Bytes'> =
| (to extends 'Bytes' ? RecursiveArray<Bytes.Bytes> : never)
| (to extends 'Hex' ? RecursiveArray<Hex.Hex> : never)
type ErrorType =
| Bytes.fromHex.ErrorType
| decodeRlpCursor.ErrorType
| Cursor.create.ErrorType
| Hex.InvalidLengthError
| Errors.GlobalErrorType
}
/** @internal */
/** @internal */
export function decodeRlpCursor<to extends 'Hex' | 'Bytes' = 'Hex'>(
cursor: Cursor.Cursor,
to: to | 'Hex' | 'Bytes' | undefined = 'Hex',
): decodeRlpCursor.ReturnType<to> {
if (cursor.bytes.length === 0)
return (
to === 'Hex' ? Hex.fromBytes(cursor.bytes) : cursor.bytes
) as decodeRlpCursor.ReturnType<to>
const prefix = cursor.readByte()
if (prefix < 0x80) cursor.decrementPosition(1)
// bytes
if (prefix < 0xc0) {
const length = readLength(cursor, prefix, 0x80)
const bytes = cursor.readBytes(length)
return (
to === 'Hex' ? Hex.fromBytes(bytes) : bytes
) as decodeRlpCursor.ReturnType<to>
}
// list
const length = readLength(cursor, prefix, 0xc0)
return readList(cursor, length, to) as {} as decodeRlpCursor.ReturnType<to>
}
/** @internal */
export declare namespace decodeRlpCursor {
type ReturnType<to extends 'Hex' | 'Bytes' = 'Hex'> = to.ReturnType<to>
type ErrorType =
| Hex.fromBytes.ErrorType
| readLength.ErrorType
| readList.ErrorType
| Errors.GlobalErrorType
}
/** @internal */
export function readLength(
cursor: Cursor.Cursor,
prefix: number,
offset: number,
) {
if (offset === 0x80 && prefix < 0x80) return 1
if (prefix <= offset + 55) return prefix - offset
if (prefix === offset + 55 + 1) return cursor.readUint8()
if (prefix === offset + 55 + 2) return cursor.readUint16()
if (prefix === offset + 55 + 3) return cursor.readUint24()
if (prefix === offset + 55 + 4) return cursor.readUint32()
throw new Errors.BaseError('Invalid RLP prefix')
}
/** @internal */
export declare namespace readLength {
type ErrorType = Errors.BaseError | Errors.GlobalErrorType
}
/** @internal */
export function readList<to extends 'Hex' | 'Bytes'>(
cursor: Cursor.Cursor,
length: number,
to: to | 'Hex' | 'Bytes',
) {
const position = cursor.position
const value: decodeRlpCursor.ReturnType<to>[] = []
while (cursor.position - position < length)
value.push(decodeRlpCursor(cursor, to))
return value
}
/** @internal */
export declare namespace readList {
type ErrorType = Errors.GlobalErrorType
}
type Encodable = {
length: number
encode(cursor: Cursor.Cursor): void
}
/**
* Encodes a {@link ox#Bytes.Bytes} or {@link ox#Hex.Hex} value into a Recursive-Length Prefix (RLP) value.
*
* @example
* ```ts twoslash
* import { Bytes, Rlp } from 'ox'
*
* Rlp.from('0x68656c6c6f20776f726c64', { as: 'Hex' })
* // @log: 0x8b68656c6c6f20776f726c64
*
* Rlp.from(Bytes.from([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), { as: 'Bytes' })
* // @log: Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
* ```
*
* @param value - The {@link ox#Bytes.Bytes} or {@link ox#Hex.Hex} value to encode.
* @param options - Options.
* @returns The RLP value.
*/
export function from<as extends 'Hex' | 'Bytes'>(
value: RecursiveArray<Bytes.Bytes> | RecursiveArray<Hex.Hex>,
options: from.Options<as>,
): from.ReturnType<as> {
const { as } = options
const encodable = getEncodable(value)
const cursor = Cursor.create(new Uint8Array(encodable.length))
encodable.encode(cursor)
if (as === 'Hex') return Hex.fromBytes(cursor.bytes) as from.ReturnType<as>
return cursor.bytes as from.ReturnType<as>
}
export declare namespace from {
type Options<as extends 'Hex' | 'Bytes'> = {
/** The type to convert the RLP value to. */
as: as | 'Hex' | 'Bytes'
}
type ReturnType<as extends 'Hex' | 'Bytes'> =
| (as extends 'Bytes' ? Bytes.Bytes : never)
| (as extends 'Hex' ? Hex.Hex : never)
type ErrorType =
| Cursor.create.ErrorType
| Hex.fromBytes.ErrorType
| Bytes.fromHex.ErrorType
| Errors.GlobalErrorType
}
/**
* Encodes a {@link ox#Bytes.Bytes} value into a Recursive-Length Prefix (RLP) value.
*
* @example
* ```ts twoslash
* import { Bytes, Rlp } from 'ox'
*
* Rlp.fromBytes(Bytes.from([139, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]))
* // @log: Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
* ```
*
* @param bytes - The {@link ox#Bytes.Bytes} value to encode.
* @param options - Options.
* @returns The RLP value.
*/
export function fromBytes<as extends 'Hex' | 'Bytes' = 'Bytes'>(
bytes: RecursiveArray<Bytes.Bytes>,
options: fromBytes.Options<as> = {},
): fromBytes.ReturnType<as> {
const { as = 'Bytes' } = options
return from(bytes, { as }) as never
}
export declare namespace fromBytes {
type Options<as extends 'Hex' | 'Bytes' = 'Bytes'> = ExactPartial<
from.Options<as>
>
type ReturnType<as extends 'Hex' | 'Bytes' = 'Bytes'> = from.ReturnType<as>
type ErrorType = from.ErrorType | Errors.GlobalErrorType
}
/**
* Encodes a {@link ox#Hex.Hex} value into a Recursive-Length Prefix (RLP) value.
*
* @example
* ```ts twoslash
* import { Rlp } from 'ox'
*
* Rlp.fromHex('0x68656c6c6f20776f726c64')
* // @log: 0x8b68656c6c6f20776f726c64
* ```
*
* @param hex - The {@link ox#Hex.Hex} value to encode.
* @param options - Options.
* @returns The RLP value.
*/
export function fromHex<as extends 'Hex' | 'Bytes' = 'Hex'>(
hex: RecursiveArray<Hex.Hex>,
options: fromHex.Options<as> = {},
): fromHex.ReturnType<as> {
const { as = 'Hex' } = options
return from(hex, { as }) as never
}
export declare namespace fromHex {
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = ExactPartial<
from.Options<as>
>
type ReturnType<as extends 'Hex' | 'Bytes' = 'Hex'> = from.ReturnType<as>
type ErrorType = from.ErrorType | Errors.GlobalErrorType
}
/////////////////////////////////////////////////////////////////////////////////
// Internal
/////////////////////////////////////////////////////////////////////////////////
function getEncodable(
bytes: RecursiveArray<Bytes.Bytes> | RecursiveArray<Hex.Hex>,
): Encodable {
if (Array.isArray(bytes))
return getEncodableList(bytes.map((x) => getEncodable(x)))
return getEncodableBytes(bytes as any)
}
function getEncodableList(list: Encodable[]): Encodable {
const bodyLength = list.reduce((acc, x) => acc + x.length, 0)
const sizeOfBodyLength = getSizeOfLength(bodyLength)
const length = (() => {
if (bodyLength <= 55) return 1 + bodyLength
return 1 + sizeOfBodyLength + bodyLength
})()
return {
length,
encode(cursor: Cursor.Cursor) {
if (bodyLength <= 55) {
cursor.pushByte(0xc0 + bodyLength)
} else {
cursor.pushByte(0xc0 + 55 + sizeOfBodyLength)
if (sizeOfBodyLength === 1) cursor.pushUint8(bodyLength)
else if (sizeOfBodyLength === 2) cursor.pushUint16(bodyLength)
else if (sizeOfBodyLength === 3) cursor.pushUint24(bodyLength)
else cursor.pushUint32(bodyLength)
}
for (const { encode } of list) {
encode(cursor)
}
},
}
}
function getEncodableBytes(bytesOrHex: Bytes.Bytes | Hex.Hex): Encodable {
const bytes =
typeof bytesOrHex === 'string' ? Bytes.fromHex(bytesOrHex) : bytesOrHex
const sizeOfBytesLength = getSizeOfLength(bytes.length)
const length = (() => {
if (bytes.length === 1 && bytes[0]! < 0x80) return 1
if (bytes.length <= 55) return 1 + bytes.length
return 1 + sizeOfBytesLength + bytes.length
})()
return {
length,
encode(cursor: Cursor.Cursor) {
if (bytes.length === 1 && bytes[0]! < 0x80) {
cursor.pushBytes(bytes)
} else if (bytes.length <= 55) {
cursor.pushByte(0x80 + bytes.length)
cursor.pushBytes(bytes)
} else {
cursor.pushByte(0x80 + 55 + sizeOfBytesLength)
if (sizeOfBytesLength === 1) cursor.pushUint8(bytes.length)
else if (sizeOfBytesLength === 2) cursor.pushUint16(bytes.length)
else if (sizeOfBytesLength === 3) cursor.pushUint24(bytes.length)
else cursor.pushUint32(bytes.length)
cursor.pushBytes(bytes)
}
},
}
}
function getSizeOfLength(length: number) {
if (length < 2 ** 8) return 1
if (length < 2 ** 16) return 2
if (length < 2 ** 24) return 3
if (length < 2 ** 32) return 4
throw new Errors.BaseError('Length is too large.')
}