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 ) }