author jdiaz5513
  import initTrace from "debug" ; import { DEFAULT_TRAVERSE_LIMIT , DEFAULT_BUFFER_SIZE } from "../constants" ; import { MSG_INVALID_FRAME_HEADER , MSG_SEGMENT_OUT_OF_BOUNDS , MSG_SEGMENT_TOO_SMALL , MSG_NO_SEGMENTS_IN_ARENA , } from "../errors" ; import { dumpBuffer , format , padToWord } from "../util" ; import { AnyArena , Arena , MultiSegmentArena , SingleSegmentArena , ArenaKind } from "./arena" ; import { pack , unpack } from "./packing" ; import { Pointer , StructCtor , PointerType , Struct } from "./pointers" ; import { Segment } from "./segment" ; import { getTargetStructSize , validate } from "./pointers/pointer" ; import { resize , initStruct } from "./pointers/struct" ; const trace = initTrace ( "capnp:message" ) ; trace ( "load" ) ; export interface _Message { readonly arena : AnyArena ; segments : Segment [ ] ; traversalLimit : number ; } export class Message { static readonly allocateSegment = allocateSegment ; static readonly dump = dump ; static readonly getRoot = getRoot ; static readonly getSegment = getSegment ; static readonly initRoot = initRoot ; static readonly readRawPointer = readRawPointer ; static readonly toArrayBuffer = toArrayBuffer ; static readonly toPackedArrayBuffer = toPackedArrayBuffer ; readonly _capnp : _Message ; constructor param { AnyArena | ArrayBufferView | ArrayBuffer } src param { boolean } packed param { boolean } singleSegment constructor ( src ? , packed = true , singleSegment = false ) { this . _capnp = initMessage ( src , packed , singleSegment ) ; if ( src && ! isAnyArena ( src ) ) preallocateSegments ( this ) ; trace ( "new %s" , this ) ; } allocateSegment ( byteLength ) { return allocateSegment ( byteLength , this ) ; } returns { string } dump ( ) { return dump ( this ) ; } template T param { StructCtor < T > } RootStruct returns { T } getRoot < T extends Struct > ( RootStruct ) { return getRoot ( RootStruct , this ) ; } param { number } id returns { Segment } getSegment ( id ) { return getSegment ( id , this ) ; } template T param { StructCtor < T > } RootStruct returns { T } initRoot < T extends Struct > ( RootStruct ) { return initRoot ( RootStruct , this ) ; } param { Pointer } src returns { void } setRoot ( src ) { setRoot ( src , this ) ; } returns { ArrayBuffer } toArrayBuffer ( ) { return toArrayBuffer ( this ) ; } returns { ArrayBuffer } toPackedArrayBuffer ( ) { return toPackedArrayBuffer ( this ) ; } toString ( ) { return `Message_arena:${ this . _capnp . arena }` ; } } export interface CreateMessageOptions { packed ? : boolean ; singleSegment ? : boolean ; } export function initMessage ( src ? , packed = true , singleSegment = false ) { if ( src === undefined ) { return { arena : new SingleSegmentArena ( ) , segments : [ ] , traversalLimit : DEFAULT_TRAVERSE_LIMIT , } ; } if ( isAnyArena ( src ) ) { return { arena : src , segments : [ ] , traversalLimit : DEFAULT_TRAVERSE_LIMIT } ; } let buf = src as ArrayBuffer ; if ( isArrayBufferView ( buf ) ) { buf = buf . buffer . slice ( buf . byteOffset , buf . byteOffset + buf . byteLength ) ; } if ( packed ) buf = unpack ( buf ) ; if ( singleSegment ) { return { arena : new SingleSegmentArena ( buf ) , segments : [ ] , traversalLimit : DEFAULT_TRAVERSE_LIMIT , } ; } return { arena : new MultiSegmentArena ( getFramedSegments ( buf ) ) , segments : [ ] , traversalLimit : DEFAULT_TRAVERSE_LIMIT , } ; } static param { ArrayBuffer } message returns { ArrayBuffer [ ] } export function getFramedSegments ( message ) { const dv = new DataView ( message ) ; const segmentCount = dv . getUint32 ( 0 , true ) + 1 ; const segments = new Array ( segmentCount ) as ArrayBuffer [ ] ; trace ( "reading %d framed segments from stream" , segmentCount ) ; let byteOffset = 4 + segmentCount * 4 ; byteOffset += byteOffset % 8 ; if ( byteOffset + segmentCount * 4 > message . byteLength ) { throw new Error ( MSG_INVALID_FRAME_HEADER ) ; } for ( let i = 0 ; i < segmentCount ; i ++ ) { const byteLength = dv . getUint32 ( 4 + i * 4 , true ) * 8 ; if ( byteOffset + byteLength > message . byteLength ) { throw new Error ( MSG_INVALID_FRAME_HEADER ) ; } segments [ i ] = message . slice ( byteOffset , byteOffset + byteLength ) ; byteOffset += byteLength ; } return segments ; } param { Message } m returns { void } export function preallocateSegments ( m ) { const numSegments = Arena . getNumSegments ( m . _capnp . arena ) ; if ( numSegments < 1 ) throw new Error ( MSG_NO_SEGMENTS_IN_ARENA ) ; m . _capnp . segments = new Array ( numSegments ) as Segment [ ] ; for ( let i = 0 ; i < numSegments ; i ++ ) { const buffer = Arena . getBuffer ( i , m . _capnp . arena ) ; const segment = new Segment ( i , m , buffer , buffer . byteLength ) ; m . _capnp . segments [ i ] = segment ; } } function isArrayBufferView ( src ) { return ( src as { byteOffset ? : number ; } ) . byteOffset !== undefined ; } function isAnyArena ( o ) { return ( o as { kind ? : ArenaKind ; } ) . kind !== undefined ; } export function allocateSegment ( byteLength , m ) { trace ( "allocating %x bytes for %s" , byteLength , m ) ; const res = Arena . allocate ( byteLength , m . _capnp . segments , m . _capnp . arena ) ; let s ; if ( res . id === m . _capnp . segments . length ) { s = new Segment ( res . id , m , res . buffer ) ; trace ( "adding new segment %s" , s ) ; m . _capnp . segments . push ( s ) ; } else if ( res . id < 0 || res . id > m . _capnp . segments . length ) { throw new Error ( format ( MSG_SEGMENT_OUT_OF_BOUNDS , res . id , m ) ) ; } else { s = m . _capnp . segments [ res . id ] ; trace ( "replacing segment %s with buffer (len:%d)" , s , res . buffer . byteLength ) ; s . replaceBuffer ( res . buffer ) ; } return s ; } export function dump ( m ) { let r = "" ; if ( m . _capnp . segments . length === 0 ) { return "================\nNo Segments\n================\n" ; } for ( let i = 0 ; i < m . _capnp . segments . length ; i ++ ) { r += `================\nSegment #${ i }\n================\n` ; const { buffer , byteLength } = m . _capnp . segments [ i ] ; const b = new Uint8Array ( buffer , 0 , byteLength ) ; r += dumpBuffer ( b ) ; } return r ; } export function getRoot < T extends Struct > ( RootStruct , m ) { const root = new RootStruct ( m . getSegment ( 0 ) , 0 ) ; validate ( PointerType . STRUCT , root ) ; const ts = getTargetStructSize ( root ) ; if ( ts . dataByteLength < RootStruct . _capnp . size . dataByteLength || ts . pointerLength < RootStruct . _capnp . size . pointerLength ) { trace ( "need to resize root struct %s" , root ) ; resize ( RootStruct . _capnp . size , root ) ; } return root ; } export function getSegment ( id , m ) { const segmentLength = m . _capnp . segments . length ; if ( id === 0 && segmentLength === 0 ) { const arenaSegments = Arena . getNumSegments ( m . _capnp . arena ) ; if ( arenaSegments === 0 ) { allocateSegment ( DEFAULT_BUFFER_SIZE , m ) ; } else { m . _capnp . segments [ 0 ] = new Segment ( 0 , m , Arena . getBuffer ( 0 , m . _capnp . arena ) ) ; } if ( ! m . _capnp . segments [ 0 ] . hasCapacity ( 8 ) ) { throw new Error ( MSG_SEGMENT_TOO_SMALL ) ; } m . _capnp . segments [ 0 ] . allocate ( 8 ) ; return m . _capnp . segments [ 0 ] ; } if ( id < 0 || id >= segmentLength ) { throw new Error ( format ( MSG_SEGMENT_OUT_OF_BOUNDS , id , m ) ) ; } return m . _capnp . segments [ id ] ; } export function initRoot < T extends Struct > ( RootStruct , m ) { const root = new RootStruct ( m . getSegment ( 0 ) , 0 ) ; initStruct ( RootStruct . _capnp . size , root ) ; trace ( "Initialized root pointer %s for %s." , root , m ) ; return root ; } param { ArrayBuffer } data returns { Pointer } export function readRawPointer ( data ) { return new Pointer ( new Message ( data ) . getSegment ( 0 ) , 0 ) ; } export function setRoot ( src , m ) { Pointer . copyFrom ( src , new Pointer ( m . getSegment ( 0 ) , 0 ) ) ; } export function toArrayBuffer ( m ) { const streamFrame = getStreamFrame ( m ) ; if ( m . _capnp . segments . length === 0 ) getSegment ( 0 , m ) ; const segments = m . _capnp . segments ; const totalLength = streamFrame . byteLength + segments . reduce ( ( l , s ) => l + padToWord ( s . byteLength ) , 0 ) ; const out = new Uint8Array ( new ArrayBuffer ( totalLength ) ) ; let o = streamFrame . byteLength ; out . set ( new Uint8Array ( streamFrame ) ) ; segments . forEach ( ( s ) => { const segmentLength = padToWord ( s . byteLength ) ; out . set ( new Uint8Array ( s . buffer , 0 , segmentLength ) , o ) ; o += segmentLength ; } ) ; return out . buffer ; } export function toPackedArrayBuffer ( m ) { const streamFrame = pack ( getStreamFrame ( m ) ) ; if ( m . _capnp . segments . length === 0 ) m . getSegment ( 0 ) ; const segments = m . _capnp . segments . map ( ( s ) => pack ( s . buffer , 0 , padToWord ( s . byteLength ) ) ) ; const totalLength = streamFrame . byteLength + segments . reduce ( ( l , s ) => l + s . byteLength , 0 ) ; const out = new Uint8Array ( new ArrayBuffer ( totalLength ) ) ; let o = streamFrame . byteLength ; out . set ( new Uint8Array ( streamFrame ) ) ; segments . forEach ( ( s ) => { out . set ( new Uint8Array ( s ) , o ) ; o += s . byteLength ; } ) ; return out . buffer ; } export function getStreamFrame ( m ) { const length = m . _capnp . segments . length ; if ( length === 0 ) { return new Float64Array ( 1 ) . buffer ; } const frameLength = 4 + length * 4 + ( 1 - ( length % 2 ) ) * 4 ; const out = new DataView ( new ArrayBuffer ( frameLength ) ) ; trace ( "Writing message stream frame with segment count: %d." , length ) ; out . setUint32 ( 0 , length - 1 , true ) ; m . _capnp . segments . forEach ( ( s , i ) => { trace ( "Message segment %d word count: %d." , s . id , s . byteLength / 8 ) ; out . setUint32 ( i * 4 + 4 , s . byteLength / 8 , true ) ; } ) ; return out . buffer ; }