author jdiaz5513
  import initTrace from "debug" ; import { LIST_SIZE_MASK , MAX_DEPTH , POINTER_DOUBLE_FAR_MASK , POINTER_TYPE_MASK } from "../../constants" ; import { bufferToHex , format , padToWord } from "../../util" ; import { ListElementSize } from "../list-element-size" ; import { ObjectSize , getByteLength , padToWord as padObjectToWord , getWordLength , getDataWordLength , } from "../object-size" ; import { Segment } from "../segment" ; import { Orphan } from "./orphan" ; import { PointerAllocationResult } from "./pointer-allocation-result" ; import { PointerType } from "./pointer-type" ; import { Message } from "../message" ; import { PTR_TRAVERSAL_LIMIT_EXCEEDED , PTR_DEPTH_LIMIT_EXCEEDED , PTR_OFFSET_OUT_OF_BOUNDS , PTR_INVALID_LIST_SIZE , PTR_INVALID_POINTER_TYPE , PTR_INVALID_FAR_TARGET , TYPE_COMPOSITE_SIZE_UNDEFINED , PTR_WRONG_POINTER_TYPE , PTR_WRONG_LIST_TYPE , INVARIANT_UNREACHABLE_CODE , } from "../../errors" ; const trace = initTrace ( "capnp:pointer" ) ; trace ( "load" ) ; export interface _PointerCtor { readonly displayName : string ; } export interface PointerCtor < T extends Pointer > { readonly _capnp : _PointerCtor ; new ( segment , byteOffset , depthLimit ? ) : T ; } export interface _Pointer { compositeIndex ? : number ; compositeList : boolean ; /**
     * A number that is decremented as nested pointers are traversed. When this hits zero errors will be thrown.
     */ depthLimit : number ; } export class export class Pointer { static readonly adopt = adopt ; static readonly copyFrom = copyFrom ; static readonly disown = disown ; static readonly dump = dump ; static readonly isNull = isNull ; static readonly _capnp : _PointerCtor = { displayName : "Pointer" as string , } ; readonly _capnp : _Pointer ; /** Offset, in bytes, from the start of the segment to the beginning of this pointer. */ byteOffset : number ; /**
     * The starting segment for this pointer's data. In the case of a far pointer, the actual content this pointer is
     * referencing will be in another segment within the same message.
     */ segment : Segment ; constructor ( segment , byteOffset , depthLimit = MAX_DEPTH ) { this . _capnp = { compositeList : false , depthLimit } ; this . segment = segment ; this . byteOffset = byteOffset ; if ( depthLimit < 1 ) { throw new Error ( format ( PTR_DEPTH_LIMIT_EXCEEDED , this ) ) ; } trackPointerAllocation ( segment . message , this ) ; if ( byteOffset < 0 || byteOffset > segment . byteLength ) { throw new Error ( format ( PTR_OFFSET_OUT_OF_BOUNDS , byteOffset ) ) ; } trace ( "new %s" , this ) ; } toString ( ) { return format ( "->%d@%a%s" , this . segment . id , this . byteOffset , dump ( this ) ) ; } } param { Orphan < Pointer > } src param { Pointer } p returns { void } export function adopt < T extends Pointer > ( src , p ) { src . _moveTo ( p ) ; } param { T } p returns { Orphan < T > } export function disown < T extends Pointer > ( p ) { return new Orphan ( p ) ; } export function dump ( p ) { const f = followFars ( p ) ; const pHex = bufferToHex ( p . segment . buffer . slice ( p . byteOffset , p . byteOffset + 8 ) ) ; if ( f . byteOffset === p . byteOffset && f . segment === p . segment ) { return pHex ; } return `${ pHex } > ${ bufferToHex ( f . segment . buffer . slice ( f . byteOffset , f . byteOffset + 8 ) ) }` ; } param { ListElementSize } elementSize param { number } length param { ObjectSize } compositeSize returns { number } export function getListByteLength ( elementSize , length , compositeSize ? ) { switch ( elementSize ) { case ListElementSize . BIT : return padToWord ( ( length + 7 ) >>> 3 ) ; case ListElementSize . BYTE : case ListElementSize . BYTE_2 : case ListElementSize . BYTE_4 : case ListElementSize . BYTE_8 : case ListElementSize . POINTER : case ListElementSize . VOID : return padToWord ( getListElementByteLength ( elementSize ) * length ) ; case ListElementSize . COMPOSITE : if ( compositeSize === undefined ) { throw new Error ( format ( PTR_INVALID_LIST_SIZE , NaN ) ) ; } return length * padToWord ( getByteLength ( compositeSize ) ) ; default : throw new Error ( PTR_INVALID_LIST_SIZE ) ; } } param { ListElementSize } elementSize returns { number } export function getListElementByteLength ( elementSize ) { switch ( elementSize ) { case ListElementSize . BIT : return NaN ; case ListElementSize . BYTE : return 1 ; case ListElementSize . BYTE_2 : return 2 ; case ListElementSize . BYTE_4 : return 4 ; case ListElementSize . BYTE_8 : case ListElementSize . POINTER : return 8 ; case ListElementSize . COMPOSITE : return NaN ; case ListElementSize . VOID : return 0 ; default : throw new Error ( format ( PTR_INVALID_LIST_SIZE , elementSize ) ) ; } } param { number } offset param { Pointer } p returns { Pointer } export function add ( offset , p ) { return new Pointer ( p . segment , p . byteOffset + offset , p . _capnp . depthLimit ) ; } param { Pointer } src param { Pointer } p returns { void } export function copyFrom ( src , p ) { if ( p . segment === src . segment && p . byteOffset === src . byteOffset ) { trace ( "ignoring copy operation from identical pointer %s" , src ) ; return ; } erase ( p ) ; if ( isNull ( src ) ) return ; switch ( getTargetPointerType ( src ) ) { case PointerType . STRUCT : copyFromStruct ( src , p ) ; break ; case PointerType . LIST : copyFromList ( src , p ) ; break ; default : throw new Error ( format ( PTR_INVALID_POINTER_TYPE , getTargetPointerType ( p ) ) ) ; } } param { Pointer } p returns { void } export function erase ( p ) { if ( isNull ( p ) ) return ; let c ; switch ( getTargetPointerType ( p ) ) { case PointerType . STRUCT : { const size = getTargetStructSize ( p ) ; c = getContent ( p ) ; c . segment . fillZeroWords ( c . byteOffset , size . dataByteLength / 8 ) ; for ( let i = 0 ; i < size . pointerLength ; i ++ ) { erase ( add ( i * 8 , c ) ) ; } break ; } case PointerType . LIST : { const elementSize = getTargetListElementSize ( p ) ; const length = getTargetListLength ( p ) ; let contentWords = padToWord ( length * getListElementByteLength ( elementSize ) ) ; c = getContent ( p ) ; if ( elementSize === ListElementSize . POINTER ) { for ( let i = 0 ; i < length ; i ++ ) { erase ( new Pointer ( c . segment , c . byteOffset + i * 8 , p . _capnp . depthLimit - 1 ) ) ; } break ; } else if ( elementSize === ListElementSize . COMPOSITE ) { const tag = add ( - 8 , c ) ; const compositeSize = getStructSize ( tag ) ; const compositeByteLength = getByteLength ( compositeSize ) ; contentWords = getOffsetWords ( tag ) ; c . segment . setWordZero ( c . byteOffset - 8 ) ; for ( let i = 0 ; i < length ; i ++ ) { for ( let j = 0 ; j < compositeSize . pointerLength ; j ++ ) { erase ( new Pointer ( c . segment , c . byteOffset + i * compositeByteLength + j * 8 , p . _capnp . depthLimit - 1 ) ) ; } } } c . segment . fillZeroWords ( c . byteOffset , contentWords ) ; break ; } case PointerType . OTHER : break ; default : throw new Error ( format ( PTR_INVALID_POINTER_TYPE , getTargetPointerType ( p ) ) ) ; } erasePointer ( p ) ; } param { Pointer } p returns { void } export function erasePointer ( p ) { if ( getPointerType ( p ) === PointerType . FAR ) { const landingPad = followFar ( p ) ; if ( isDoubleFar ( p ) ) { landingPad . segment . setWordZero ( landingPad . byteOffset + 8 ) ; } landingPad . segment . setWordZero ( landingPad . byteOffset ) ; } p . segment . setWordZero ( p . byteOffset ) ; } param { Pointer } p returns { Pointer } export function followFar ( p ) { const targetSegment = p . segment . message . getSegment ( p . segment . getUint32 ( p . byteOffset + 4 ) ) ; const targetWordOffset = p . segment . getUint32 ( p . byteOffset ) >>> 3 ; return new Pointer ( targetSegment , targetWordOffset * 8 , p . _capnp . depthLimit - 1 ) ; } param { Pointer } p returns { Pointer } export function followFars ( p ) { if ( getPointerType ( p ) === PointerType . FAR ) { const landingPad = followFar ( p ) ; if ( isDoubleFar ( p ) ) landingPad . byteOffset += 8 ; return landingPad ; } return p ; } export function getCapabilityId ( p ) { return p . segment . getUint32 ( p . byteOffset + 4 ) ; } function isCompositeList ( p ) { return getTargetPointerType ( p ) === PointerType . LIST && getTargetListElementSize ( p ) === ListElementSize . COMPOSITE ; } param { Pointer } p param { boolean } ignoreCompositeIndex returns { Pointer } export function getContent ( p , ignoreCompositeIndex ? ) { let c ; if ( isDoubleFar ( p ) ) { const landingPad = followFar ( p ) ; c = new Pointer ( p . segment . message . getSegment ( getFarSegmentId ( landingPad ) ) , getOffsetWords ( landingPad ) * 8 ) ; } else { const target = followFars ( p ) ; c = new Pointer ( target . segment , target . byteOffset + 8 + getOffsetWords ( target ) * 8 ) ; } if ( isCompositeList ( p ) ) c . byteOffset += 8 ; if ( ! ignoreCompositeIndex && p . _capnp . compositeIndex !== undefined ) { c . byteOffset -= 8 ; c . byteOffset += 8 + p . _capnp . compositeIndex * getByteLength ( padObjectToWord ( getStructSize ( c ) ) ) ; } return c ; } param { Pointer } p returns { number } export function getFarSegmentId ( p ) { return p . segment . getUint32 ( p . byteOffset + 4 ) ; } param { Pointer } p returns { ListElementSize } export function getListElementSize ( p ) { return p . segment . getUint32 ( p . byteOffset + 4 ) & LIST_SIZE_MASK ; } param { Pointer } p returns { number } export function getListLength ( p ) { return p . segment . getUint32 ( p . byteOffset + 4 ) >>> 3 ; } param { Pointer } p returns { number } export function getOffsetWords ( p ) { const o = p . segment . getInt32 ( p . byteOffset ) ; return o & 2 ? o >> 3 : o >> 2 ; } param { Pointer } p returns { PointerType } export function getPointerType ( p ) { return p . segment . getUint32 ( p . byteOffset ) & POINTER_TYPE_MASK ; } param { Pointer } p returns { number } export function getStructDataWords ( p ) { return p . segment . getUint16 ( p . byteOffset + 4 ) ; } param { Pointer } p returns { number } export function getStructPointerLength ( p ) { return p . segment . getUint16 ( p . byteOffset + 6 ) ; } param { Pointer } p returns { ObjectSize } export function getStructSize ( p ) { return new ObjectSize ( getStructDataWords ( p ) * 8 , getStructPointerLength ( p ) ) ; } param { Pointer } p returns { Pointer } export function getTargetCompositeListTag ( p ) { const c = getContent ( p ) ; c . byteOffset -= 8 ; return c ; } param { Pointer } p returns { ObjectSize } export function getTargetCompositeListSize ( p ) { return getStructSize ( getTargetCompositeListTag ( p ) ) ; } param { Pointer } p returns { ListElementSize } export function getTargetListElementSize ( p ) { return getListElementSize ( followFars ( p ) ) ; } param { Pointer } p returns { number } export function getTargetListLength ( p ) { const t = followFars ( p ) ; if ( getListElementSize ( t ) === ListElementSize . COMPOSITE ) { return getOffsetWords ( getTargetCompositeListTag ( p ) ) ; } return getListLength ( t ) ; } param { Pointer } p returns { PointerType } export function getTargetPointerType ( p ) { const t = getPointerType ( followFars ( p ) ) ; if ( t === PointerType . FAR ) throw new Error ( format ( PTR_INVALID_FAR_TARGET , p ) ) ; return t ; } param { Pointer } p returns { ObjectSize } export function getTargetStructSize ( p ) { return getStructSize ( followFars ( p ) ) ; } param { Segment } contentSegment param { number } contentOffset param { Pointer } p returns { PointerAllocationResult } export function initPointer ( contentSegment , contentOffset , p ) { if ( p . segment !== contentSegment ) { trace ( "Initializing far pointer %s -> %s." , p , contentSegment ) ; if ( ! contentSegment . hasCapacity ( 8 ) ) { const landingPad = p . segment . allocate ( 16 ) ; trace ( "GAH! Initializing double-far pointer in %s from %s -> %s." , p , contentSegment , landingPad ) ; setFarPointer ( true , landingPad . byteOffset / 8 , landingPad . segment . id , p ) ; setFarPointer ( false , contentOffset / 8 , contentSegment . id , landingPad ) ; landingPad . byteOffset += 8 ; return new PointerAllocationResult ( landingPad , 0 ) ; } const landingPad = contentSegment . allocate ( 8 ) ; if ( landingPad . segment . id !== contentSegment . id ) { throw new Error ( INVARIANT_UNREACHABLE_CODE ) ; } setFarPointer ( false , landingPad . byteOffset / 8 , landingPad . segment . id , p ) ; return new PointerAllocationResult ( landingPad , ( contentOffset - landingPad . byteOffset - 8 ) / 8 ) ; } trace ( "Initializing intra-segment pointer %s -> %a." , p , contentOffset ) ; return new PointerAllocationResult ( p , ( contentOffset - p . byteOffset - 8 ) / 8 ) ; } param { Pointer } p returns { boolean } export function isDoubleFar ( p ) { return getPointerType ( p ) === PointerType . FAR && ( p . segment . getUint32 ( p . byteOffset ) & POINTER_DOUBLE_FAR_MASK ) !== 0 ; } param { Pointer } p returns { boolean } export function isNull ( p ) { return p . segment . isWordZero ( p . byteOffset ) ; } param { Pointer } dst param { Pointer } src returns { void } export function relocateTo ( dst , src ) { const t = followFars ( src ) ; const lo = t . segment . getUint8 ( t . byteOffset ) & 0x03 ; const hi = t . segment . getUint32 ( t . byteOffset + 4 ) ; erase ( dst ) ; const res = initPointer ( t . segment , t . byteOffset + 8 + getOffsetWords ( t ) * 8 , dst ) ; res . pointer . segment . setUint32 ( res . pointer . byteOffset , lo | ( res . offsetWords << 2 ) ) ; res . pointer . segment . setUint32 ( res . pointer . byteOffset + 4 , hi ) ; erasePointer ( src ) ; } param { boolean } doubleFar param { number } offsetWords param { number } segmentId param { Pointer } p returns { void } export function setFarPointer ( doubleFar , offsetWords , segmentId , p ) { const A = PointerType . FAR ; const B = doubleFar ? 1 : 0 ; const C = offsetWords ; const D = segmentId ; p . segment . setUint32 ( p . byteOffset , A | ( B << 2 ) | ( C << 3 ) ) ; p . segment . setUint32 ( p . byteOffset + 4 , D ) ; } param { number } capId param { Pointer } p returns { void } export function setInterfacePointer ( capId , p ) { p . segment . setUint32 ( p . byteOffset , PointerType . OTHER ) ; p . segment . setUint32 ( p . byteOffset + 4 , capId ) ; } param { number } offsetWords param { ListElementSize } size param { number } length param { Pointer } p param { ObjectSize } compositeSize returns { void } export function setListPointer ( offsetWords , size , length , p , compositeSize ? ) { const A = PointerType . LIST ; const B = offsetWords ; const C = size ; let D = length ; if ( size === ListElementSize . COMPOSITE ) { if ( compositeSize === undefined ) { throw new TypeError ( TYPE_COMPOSITE_SIZE_UNDEFINED ) ; } D *= getWordLength ( compositeSize ) ; } p . segment . setUint32 ( p . byteOffset , A | ( B << 2 ) ) ; p . segment . setUint32 ( p . byteOffset + 4 , C | ( D << 3 ) ) ; } param { number } offsetWords param { ObjectSize } size param { Pointer } p returns { void } export function setStructPointer ( offsetWords , size , p ) { const A = PointerType . STRUCT ; const B = offsetWords ; const C = getDataWordLength ( size ) ; const D = size . pointerLength ; p . segment . setUint32 ( p . byteOffset , A | ( B << 2 ) ) ; p . segment . setUint16 ( p . byteOffset + 4 , C ) ; p . segment . setUint16 ( p . byteOffset + 6 , D ) ; } param { PointerType } pointerType param { Pointer } p param { ListElementSize } elementSize returns { void } export function validate ( pointerType , p , elementSize ? ) { if ( isNull ( p ) ) return ; const t = followFars ( p ) ; const A = t . segment . getUint32 ( t . byteOffset ) & POINTER_TYPE_MASK ; if ( A !== pointerType ) { throw new Error ( format ( PTR_WRONG_POINTER_TYPE , p , pointerType ) ) ; } if ( elementSize !== undefined ) { const C = t . segment . getUint32 ( t . byteOffset + 4 ) & LIST_SIZE_MASK ; if ( C !== elementSize ) { throw new Error ( format ( PTR_WRONG_LIST_TYPE , p , ListElementSize [ elementSize ] ) ) ; } } } export function copyFromList ( src , dst ) { if ( dst . _capnp . depthLimit <= 0 ) throw new Error ( PTR_DEPTH_LIMIT_EXCEEDED ) ; const srcContent = getContent ( src ) ; const srcElementSize = getTargetListElementSize ( src ) ; const srcLength = getTargetListLength ( src ) ; let srcCompositeSize ; let srcStructByteLength ; let dstContent ; if ( srcElementSize === ListElementSize . POINTER ) { dstContent = dst . segment . allocate ( srcLength << 3 ) ; for ( let i = 0 ; i < srcLength ; i ++ ) { const srcPtr = new Pointer ( srcContent . segment , srcContent . byteOffset + ( i << 3 ) , src . _capnp . depthLimit - 1 ) ; const dstPtr = new Pointer ( dstContent . segment , dstContent . byteOffset + ( i << 3 ) , dst . _capnp . depthLimit - 1 ) ; copyFrom ( srcPtr , dstPtr ) ; } } else if ( srcElementSize === ListElementSize . COMPOSITE ) { srcCompositeSize = padObjectToWord ( getTargetCompositeListSize ( src ) ) ; srcStructByteLength = getByteLength ( srcCompositeSize ) ; dstContent = dst . segment . allocate ( getByteLength ( srcCompositeSize ) * srcLength + 8 ) ; dstContent . segment . copyWord ( dstContent . byteOffset , srcContent . segment , srcContent . byteOffset - 8 ) ; if ( srcCompositeSize . dataByteLength > 0 ) { const wordLength = getWordLength ( srcCompositeSize ) * srcLength ; dstContent . segment . copyWords ( dstContent . byteOffset + 8 , srcContent . segment , srcContent . byteOffset , wordLength ) ; } for ( let i = 0 ; i < srcLength ; i ++ ) { for ( let j = 0 ; j < srcCompositeSize . pointerLength ; j ++ ) { const offset = i * srcStructByteLength + srcCompositeSize . dataByteLength + ( j << 3 ) ; const srcPtr = new Pointer ( srcContent . segment , srcContent . byteOffset + offset , src . _capnp . depthLimit - 1 ) ; const dstPtr = new Pointer ( dstContent . segment , dstContent . byteOffset + offset + 8 , dst . _capnp . depthLimit - 1 ) ; copyFrom ( srcPtr , dstPtr ) ; } } } else { const byteLength = padToWord ( srcElementSize === ListElementSize . BIT ? ( srcLength + 7 ) >>> 3 : getListElementByteLength ( srcElementSize ) * srcLength ) ; const wordLength = byteLength >>> 3 ; dstContent = dst . segment . allocate ( byteLength ) ; dstContent . segment . copyWords ( dstContent . byteOffset , srcContent . segment , srcContent . byteOffset , wordLength ) ; } const res = initPointer ( dstContent . segment , dstContent . byteOffset , dst ) ; setListPointer ( res . offsetWords , srcElementSize , srcLength , res . pointer , srcCompositeSize ) ; } export function copyFromStruct ( src , dst ) { if ( dst . _capnp . depthLimit <= 0 ) throw new Error ( PTR_DEPTH_LIMIT_EXCEEDED ) ; const srcContent = getContent ( src ) ; const srcSize = getTargetStructSize ( src ) ; const srcDataWordLength = getDataWordLength ( srcSize ) ; const dstContent = dst . segment . allocate ( getByteLength ( srcSize ) ) ; dstContent . segment . copyWords ( dstContent . byteOffset , srcContent . segment , srcContent . byteOffset , srcDataWordLength ) ; for ( let i = 0 ; i < srcSize . pointerLength ; i ++ ) { const offset = srcSize . dataByteLength + i * 8 ; const srcPtr = new Pointer ( srcContent . segment , srcContent . byteOffset + offset , src . _capnp . depthLimit - 1 ) ; const dstPtr = new Pointer ( dstContent . segment , dstContent . byteOffset + offset , dst . _capnp . depthLimit - 1 ) ; copyFrom ( srcPtr , dstPtr ) ; } if ( dst . _capnp . compositeList ) return ; const res = initPointer ( dstContent . segment , dstContent . byteOffset , dst ) ; setStructPointer ( res . offsetWords , srcSize , res . pointer ) ; } param { Message } message param { Pointer } p returns { void } export function trackPointerAllocation ( message , p ) { message . _capnp . traversalLimit -= 8 ; if ( message . _capnp . traversalLimit <= 0 ) { throw new Error ( format ( PTR_TRAVERSAL_LIMIT_EXCEEDED , p ) ) ; } }