/**
 * Data representation which is followed(zipIt Protocol) here is described in this document:
 * https://microsoft.sharepoint-df.com/:w:/t/ODSPFileStore/ER06b64K_XdDjEyAKl-UT60BJiId39SCVkYSyo_2pvH9gQ?e=KYQ0c5
*/ import { assert , IsoBuffer , Uint8ArrayToArrayBuffer , Uint8ArrayToString } from "@fluidframework/common-utils" ; import { NonRetryableError } from "@fluidframework/driver-utils" ; import { DriverErrorType } from "@fluidframework/driver-definitions" ; import { ReadBuffer } from "./ReadBufferUtils" ; /**
 * Control codes used by tree serialization / decentralization code. Same as on server. These can be found on
 * filestore code on server too at above link.
 */ export enum MarkerCodes { BoolTrue = 11 , BoolFalse = 12 , StringEmpty = 13 , String8Length = 14 , String16Length = 15 , String32Length = 16 , ConstString8Id = 17 , ConstString16Id = 18 , ConstString32Id = 19 , ConstStringDeclare = 20 , ConstStringDeclareBig = 21 , Int0 = 1 , UInt8 = 3 , UInt16 = 5 , UInt32 = 7 , UInt64 = 9 , Int8 = 2 , Int16 = 4 , Int32 = 6 , Int64 = 8 , BinaryEmpty = 32 , BinarySingle8 = 33 , BinarySingle16 = 34 , BinarySingle32 = 35 , BinarySingle64 = 36 } /**
 * Control codes used by tree serialization / decentralization code. They mark the start of sections.
 */ export enum MarkerCodesStart { "list" = 49 , "set" = 51 } /**
 * Control codes used by tree serialization / decentralization code. They mark the end of sections.
 */ export enum MarkerCodesEnd { "list" = 50 , "set" = 52 } /**
 * This contains mapping of Marker Codes to number of bytes in which the corresponding data
 * will be stored.
*/ export const codeToBytesMap = { 1 : 0 , 2 : 1 , 3 : 1 , 4 : 2 , 5 : 2 , 6 : 4 , 7 : 4 , 8 : 8 , 9 : 8 , 13 : 0 , 14 : 1 , 15 : 2 , 16 : 4 , 17 : 1 , 18 : 2 , 19 : 4 , 20 : 1 , 21 : 4 , 32 : 0 , 33 : 1 , 34 : 2 , 35 : 4 , 36 : 8 } ; export function getValueSafely ( map , key ) { const val = map [ key ] ; assert ( val !== undefined , 0x287 ) ; return val ; } export function getAndValidateNodeProps ( node , props , enforceAllProps = true ) { const propSet = new Set ( props ) ; const res = { } ; for ( const [ keyNode , valueNode ] of node . iteratePairs ( ) ) { assertBlobCoreInstance ( keyNode , "keynode should be a blob" ) ; const keyStr = keyNode . toString ( ) ; if ( propSet . has ( keyStr ) ) { propSet . delete ( keyStr ) ; res [ keyStr ] = valueNode ; } } if ( enforceAllProps ) { assert ( propSet . size === 0 , 0x288 ) ; } return res ; } export function iteratePairs < T > ( it ) { const res = { next : ( ) => { const a = it . next ( ) ; if ( a . done ) { return { value : undefined , done : true } ; } const b = it . next ( ) ; assert ( b . done !== true , 0x22b ) ; return { value : [ a . value , b . value ] , done : b . done } ; } , [ Symbol . iterator ] : ( ) => { return res ; } } ; return res ; } param obj export function iterate < T > ( obj ) { return obj [ Symbol . iterator ] ( ) ; } /**
 * Base class to represent binary blob element.
 * Binary blob is one of three types supported as a leaf node of a tree.
 * Note: concrete implementations (derived classes) are not exposed from this module
 */ export abstract class BlobCore { public abstract get buffer ( ) : Uint8Array ; public get arrayBuffer ( ) : ArrayBufferLike { return Uint8ArrayToArrayBuffer ( this . buffer ) ; } param constString param useUtf8Code constructor ( public readonly constString , public readonly useUtf8Code = false ) { } public toString ( ) { return Uint8ArrayToString ( this . buffer , "utf-8" ) ; } } /**
 * "deep copy" blob, holds to binary data passed in
 * It is called deep copy as a counter-part to BlobShallowCopy, which
 * is a reference to underlying binary stream (ReadBuffer).
*/ class BlobDeepCopy extends BlobCore { param data param constString param useUtf8Code constructor ( protected readonly data , constString , useUtf8Code = false ) { super ( constString , useUtf8Code ) ; } public get buffer ( ) { return this . data ; } public static read ( buffer , lengthLen , constString ) { const length = buffer . read ( lengthLen ) ; const data = new Uint8Array ( length ) ; for ( let counter = 0 ; counter < length ; counter ++ ) { data [ counter ] = buffer . read ( ) ; } return new BlobDeepCopy ( data , constString ) ; } } /**
 * Shallow copy blob, keeps a reference to portion of ReadBuffer
 * it was constructed from. It takes much less memory compared to BlobDeepCopy
 */ export class BlobShallowCopy extends BlobCore { param data param start param end param constString constructor ( protected data , protected start , protected end , constString ) { super ( constString ) ; } public get buffer ( ) { return this . data . buffer . subarray ( this . start , this . end ) ; } public static read ( buffer , lengthLen , constString ) { const length = buffer . read ( lengthLen ) ; const pos = buffer . pos ; buffer . skip ( length ) ; return new BlobShallowCopy ( buffer , pos , pos + length , constString ) ; } } export const addStringProperty = ( node , a , b , encodeValAsConstString = false ) => { node . addString ( a , true ) ; node . addString ( b , encodeValAsConstString ) ; } ; export const addNumberProperty = ( node , a , b ) => { node . addString ( a , true ) ; node . addNumber ( b ) ; } ; export const addBoolProperty = ( node , a , b ) => { node . addString ( a , true ) ; node . addBool ( b ) ; } ; /**
 * Three leaf types supported by tree:
 * 1. Node (sub-tree)
 * 2. binary blob
 * 3. integer
 * 4. boolean
 */ export type NodeTypes = NodeCore | BlobCore | number | boolean ; export type NodeCoreTypes = "list" | "set" ; /**
 * Node - node in the tree (non-leaf element of the tree)
 */ export class NodeCore { private readonly children : NodeTypes [ ] = [ ] ; public get nodes ( ) { return this . children ; } constructor ( public type = "set" ) { } public [ Symbol . iterator ] ( ) { return this . children [ Symbol . iterator ] ( ) ; } public iteratePairs ( ) { assert ( ( this . length % 2 ) === 0 , 0x22c ) ; return iteratePairs ( iterate ( this ) ) ; } public get length ( ) { return this . children . length ; } public get ( index ) { return this . children [ index ] ; } public getString ( index ) { const node = this . children [ index ] ; assertBlobCoreInstance ( node , "getString should return stringblob" ) ; return node . toString ( ) ; } public getBlob ( index ) { const node = this . children [ index ] ; assertBlobCoreInstance ( node , "getBlob should return a blob" ) ; return node ; } public getNode ( index ) { const node = this . children [ index ] ; assertNodeCoreInstance ( node , "getNode should return a node" ) ; return node ; } public getNumber ( index ) { const node = this . children [ index ] ; assertNumberInstance ( node , "getNumber should return a number" ) ; return node ; } public getBool ( index ) { const node = this . children [ index ] ; assertBoolInstance ( node , "getBool should return a boolean" ) ; return node ; } public addNode ( type ? ) { const node = new NodeCore ( type ) ; this . children . push ( node ) ; return node ; } public addBlob ( blob , constString , useUtf8Code = false ) { this . children . push ( new BlobDeepCopy ( blob , constString , useUtf8Code ) ) ; } public addString ( payload , constString ) { this . addBlob ( IsoBuffer . from ( payload , "utf-8" ) , constString , true ) ; } public addNumber ( payload ) { assert ( Number . isInteger ( payload ) , 0x231 ) ; assert ( payload !== undefined && payload >= 0 , 0x232 ) ; this . children . push ( payload ) ; } public addBool ( payload ) { this . children . push ( payload ) ; } param buffer protected load ( buffer , dictionary ) { for ( ; ! buffer . eof ; ) { let childValue ; const code = buffer . read ( ) ; switch ( code ) { case MarkerCodesStart . list : case MarkerCodesStart . set : { childValue = new NodeCore ( code === MarkerCodesStart . set ? "set" : "list" ) ; this . children . push ( childValue ) ; childValue . load ( buffer , dictionary ) ; break ; } case MarkerCodes . ConstStringDeclare : case MarkerCodes . ConstStringDeclareBig : { const stringId = buffer . read ( getValueSafely ( codeToBytesMap , code ) ) ; const constString = BlobShallowCopy . read ( buffer , getValueSafely ( codeToBytesMap , code ) , true ) ; dictionary [ stringId ] = constString ; break ; } case MarkerCodes . ConstString8Id : case MarkerCodes . ConstString16Id : case MarkerCodes . ConstString32Id : { const stringId = buffer . read ( getValueSafely ( codeToBytesMap , code ) ) ; childValue = dictionary [ stringId ] ; this . children . push ( childValue ) ; break ; } case MarkerCodes . StringEmpty : case MarkerCodes . String8Length : case MarkerCodes . String16Length : case MarkerCodes . String32Length : case MarkerCodes . BinaryEmpty : case MarkerCodes . BinarySingle8 : case MarkerCodes . BinarySingle16 : case MarkerCodes . BinarySingle32 : case MarkerCodes . BinarySingle64 : { childValue = BlobShallowCopy . read ( buffer , getValueSafely ( codeToBytesMap , code ) , false ) ; this . children . push ( childValue ) ; break ; } case MarkerCodes . Int0 : { childValue = 0 ; this . children . push ( childValue ) ; break ; } case MarkerCodes . UInt8 : case MarkerCodes . UInt16 : case MarkerCodes . UInt32 : case MarkerCodes . UInt64 : case MarkerCodes . Int8 : case MarkerCodes . Int16 : case MarkerCodes . Int32 : case MarkerCodes . Int64 : { childValue = buffer . read ( getValueSafely ( codeToBytesMap , code ) ) ; this . children . push ( childValue ) ; break ; } case MarkerCodes . BoolTrue : this . children . push ( true ) ; break ; case MarkerCodes . BoolFalse : this . children . push ( false ) ; break ; case MarkerCodesEnd . list : case MarkerCodesEnd . set : return ; default : throw new Error ( `Invalid code: ${ code }` ) ; } } } } /**
 * TreeBuilder - Root of the tree.
 * Provides loading and serialization capabilities.
 */ export class TreeBuilder extends NodeCore { static load ( buffer ) { const builder = new TreeBuilder ( ) ; const dictionary = new Array < BlobCore > ( ) ; builder . load ( buffer , dictionary ) ; assert ( buffer . eof , 0x233 ) ; return builder ; } } export function assertBlobCoreInstance ( node , message ) { if ( node instanceof BlobCore ) { return ; } throwBufferParseException ( node , "BlobCore" , message ) ; } export function assertNodeCoreInstance ( node , message ) { if ( node instanceof NodeCore ) { return ; } throwBufferParseException ( node , "NodeCore" , message ) ; } export function assertNumberInstance ( node , message ) { if ( typeof node === "number" ) { return ; } throwBufferParseException ( node , "Number" , message ) ; } export function assertBoolInstance ( node , message ) { if ( typeof node === "boolean" ) { return ; } throwBufferParseException ( node , "Boolean" , message ) ; } function throwBufferParseException ( node , expectedNodeType , message ) { throw new NonRetryableError ( "bufferParsingException" , message , DriverErrorType . incorrectServerResponse , { nodeType : getNodeType ( node ) , expectedNodeType } ) ; } function getNodeType ( value ) { if ( typeof value === "number" ) { return "Number" ; } else if ( value instanceof BlobCore ) { return "BlobCore" ; } else if ( value instanceof NodeCore ) { return "NodeCore" ; } else if ( typeof value === "boolean" ) { return "Boolean" ; } return "UnknownType" ; } type NodeType = "Number" | "BlobCore" | "NodeCore" | "Boolean" | "UnknownType" ;