241 lines
6.1 KiB
JavaScript
241 lines
6.1 KiB
JavaScript
import { limits } from './limits'
|
|
import { sizedDataView, filledDataView, memcpy } from './mem'
|
|
import { Type } from './Type'
|
|
import { Int } from './Int'
|
|
import { ConstString } from './ConstString'
|
|
import { ConstArray } from './ConstArray'
|
|
import { ConstDataView } from './ConstDataView'
|
|
import { Sequence } from './Sequence'
|
|
import { Struct } from './Struct'
|
|
|
|
export {
|
|
limits,
|
|
sizedDataView,
|
|
filledDataView,
|
|
memcpy,
|
|
Type,
|
|
Int,
|
|
ConstString,
|
|
ConstArray,
|
|
ConstDataView,
|
|
Sequence,
|
|
Struct,
|
|
}
|
|
|
|
export function serialize(dv, src, ...types) {
|
|
const [type, ...inner_types] = types
|
|
|
|
if (type == Boolean && typeof src == 'boolean') {
|
|
if (dv.byteLength < 1) {
|
|
throw new Error('Boolean too small buffer')
|
|
}
|
|
dv.setUint8(0, src ? 1 : 0)
|
|
return
|
|
}
|
|
if (type == Number && typeof src == 'number') {
|
|
if (dv.byteLength < 8) {
|
|
throw new Error('Number too small buffer')
|
|
}
|
|
dv.setFloat64(0, src)
|
|
return
|
|
}
|
|
if (type == String && typeof src == 'string') {
|
|
const encoder = new TextEncoder('utf-8')
|
|
let encoded = new DataView(encoder.encode(src).buffer)
|
|
|
|
if (dv.byteLength < 4 + encoded.byteLength) {
|
|
throw new Error('String too small buffer')
|
|
}
|
|
if (encoded.byteLength > limits.u32.MAX_VALUE) {
|
|
throw new Error('String is too long')
|
|
}
|
|
|
|
dv.setUint32(0, encoded.byteLength)
|
|
|
|
const frame = new DataView(dv.buffer, dv.byteOffset + 4)
|
|
memcpy(frame, encoded)
|
|
return
|
|
}
|
|
if (type == Array && Array.isArray(src)) {
|
|
const item_headless = isHeadless(...inner_types)
|
|
const item_head_size = sizeofHead(...inner_types)
|
|
const size = src.length
|
|
|
|
if (dv.byteLength < sizeof(src, ...types)) {
|
|
throw new Error('Array, ' + inner_types.join(', ') + ' too small buffer')
|
|
}
|
|
if (size > limits.u32.MAX_VALUE) {
|
|
throw new Error('Array, ' + inner_types.join(', ') + ' is too long')
|
|
}
|
|
|
|
dv.setUint32(0, size)
|
|
|
|
let offset = 4 + item_head_size * size
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const item_head_frame = new DataView(
|
|
dv.buffer,
|
|
dv.byteOffset + 4 + item_head_size * i,
|
|
)
|
|
if (item_headless) {
|
|
item_head_frame.setUint32(0, offset)
|
|
const item_frame = new DataView(dv.buffer, dv.byteOffset + offset)
|
|
serialize(item_frame, src[i], ...inner_types)
|
|
offset += sizeof(src[i], ...inner_types)
|
|
} else {
|
|
serialize(item_head_frame, src[i], ...inner_types)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
if (type == DataView && src instanceof DataView) {
|
|
if (dv.byteLength < 4 + src.byteLength) {
|
|
throw new Error('DataView too small buffer')
|
|
}
|
|
if (src.byteLength > limits.u32.MAX_VALUE) {
|
|
throw new Error('DataView data is too long')
|
|
}
|
|
|
|
dv.setUint32(0, src.byteLength)
|
|
|
|
const frame = new DataView(dv.buffer, dv.byteOffset + 4)
|
|
memcpy(frame, src)
|
|
return
|
|
}
|
|
if (type instanceof Type) {
|
|
type.serialize(dv, src, ...inner_types)
|
|
return
|
|
}
|
|
}
|
|
|
|
export function parse(dv, ...types) {
|
|
const [type, ...inner_types] = types
|
|
|
|
if (type == Boolean) {
|
|
return !!dv.getUint8(0)
|
|
}
|
|
if (type == Number) {
|
|
return dv.getFloat64(0)
|
|
}
|
|
if (type == String) {
|
|
const size = dv.getUint32(0)
|
|
const frame = new DataView(dv.buffer, 4 + dv.byteOffset, size)
|
|
const decoder = new TextDecoder('utf-8')
|
|
return decoder.decode(frame)
|
|
}
|
|
if (type == Array) {
|
|
const item_headless = isHeadless(...inner_types)
|
|
const item_head_size = sizeofHead(...inner_types)
|
|
const size = dv.getUint32(0)
|
|
const array = Array(size)
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const item_head_frame = new DataView(
|
|
dv.buffer,
|
|
dv.byteOffset + 4 + item_head_size * i,
|
|
)
|
|
if (item_headless) {
|
|
const offset = item_head_frame.getUint32(0)
|
|
const item_frame = new DataView(dv.buffer, dv.byteOffset + offset)
|
|
array[i] = parse(item_frame, ...inner_types)
|
|
} else {
|
|
array[i] = parse(item_head_frame, ...inner_types)
|
|
}
|
|
}
|
|
|
|
return array
|
|
}
|
|
if (type == DataView) {
|
|
const size = dv.getUint32(0)
|
|
const res_buffer = new ArrayBuffer(size)
|
|
const res_dv = new DataView(res_buffer)
|
|
|
|
const frame = new DataView(dv.buffer, dv.byteOffset + 4)
|
|
memcpy(res_dv, frame)
|
|
|
|
return res_dv
|
|
}
|
|
if (type instanceof Type) {
|
|
return type.parse(dv, ...inner_types)
|
|
}
|
|
}
|
|
|
|
export function isHeadless(...args) {
|
|
const [arg, ...inner_args] = args
|
|
if (arg instanceof Type) {
|
|
return arg.isHeadless(...inner_args)
|
|
}
|
|
return (
|
|
arg == Array ||
|
|
arg == String ||
|
|
arg == DataView ||
|
|
Array.isArray(arg) ||
|
|
typeof arg == 'string' ||
|
|
arg instanceof DataView
|
|
)
|
|
}
|
|
|
|
export function sizeofHead(...args) {
|
|
if (isHeadless(...args)) {
|
|
return 4
|
|
} else {
|
|
return sizeof(...args)
|
|
}
|
|
}
|
|
|
|
export function sizeof(...args) {
|
|
const [arg, ...inner_args] = args
|
|
const [arg2, ...inner_args2] = inner_args
|
|
|
|
if (arg == Boolean || (arg2 == Boolean && typeof arg == 'boolean')) {
|
|
return 1
|
|
}
|
|
if (arg == Number || (arg2 == Number && typeof arg == 'number')) {
|
|
return 8
|
|
}
|
|
if (arg2 == String && typeof arg == 'string') {
|
|
const encoder = new TextEncoder('utf-8')
|
|
return 4 + encoder.encode(arg).byteLength
|
|
}
|
|
if (arg2 == Array && Array.isArray(arg)) {
|
|
const fixed_size = 4 + sizeofHead(...inner_args2) * arg.length
|
|
if (isHeadless(...inner_args2)) {
|
|
let variable_size = 0
|
|
for (const item of arg) {
|
|
variable_size += sizeof(item, ...inner_args2)
|
|
}
|
|
|
|
return fixed_size + variable_size
|
|
} else {
|
|
return fixed_size
|
|
}
|
|
}
|
|
if (arg2 == DataView && arg instanceof DataView) {
|
|
return 4 + arg.byteLength
|
|
}
|
|
if (arg instanceof Type) {
|
|
return arg.sizeof(...inner_args)
|
|
}
|
|
if (arg2 instanceof Type) {
|
|
return arg2.sizeof(arg, ...inner_args2)
|
|
}
|
|
throw new Error(
|
|
'unknown size of ' +
|
|
args.map((arg) => {
|
|
if (typeof arg == 'function') return arg.name
|
|
return arg
|
|
}),
|
|
)
|
|
}
|
|
|
|
export function isSerializableType(type) {
|
|
return (
|
|
type == Boolean ||
|
|
type == Number ||
|
|
type == String ||
|
|
type == Array ||
|
|
type == DataView ||
|
|
type instanceof Type
|
|
)
|
|
}
|