/**
 * This implementation is largely taken from the Apache project and reimplemented in TypeScript.
 *
 * The original project can be found here:
 * https://github.com/apache/thrift/blob/master/lib/nodejs/lib/thrift/compact_protocol.js
 */ import { TProtocolException , TProtocolExceptionType } from '../errors' ; import { readDoubleLE , writeDoubleLE } from '../binary' ; import { defaultLogger } from '../logger' ; import { TTransport } from '../transports' ; import { IInt64 , Int64 , isInt64 , IThriftField , IThriftList , IThriftMap , IThriftMessage , IThriftSet , IThriftStruct , LogFunction , MessageType , TType , } from '../types' ; import { TProtocol } from './TProtocol' ; const PROTOCOL_ID = - 126 ; const VERSION_N = 1 ; const VERSION_MASK = 0x1f ; const TYPE_MASK = - 32 ; const TYPE_BITS = 7 ; const TYPE_SHIFT_AMOUNT = 5 ; readonly property property property property property property property property property property property property property export enum CompactType { STOP = 0x00 , BOOLEAN_TRUE = 0x01 , BOOLEAN_FALSE = 0x02 , BYTE = 0x03 , I16 = 0x04 , I32 = 0x05 , I64 = 0x06 , DOUBLE = 0x07 , BINARY = 0x08 , LIST = 0x09 , SET = 0x0a , MAP = 0x0b , STRUCT = 0x0c } readonly const TTypeToCType = [ CompactType . STOP , 0 , CompactType . BOOLEAN_TRUE , CompactType . BYTE , CompactType . DOUBLE , 0 , CompactType . I16 , 0 , CompactType . I32 , 0 , CompactType . I64 , CompactType . BINARY , CompactType . STRUCT , CompactType . MAP , CompactType . SET , CompactType . LIST , ] ; export class CompactProtocol extends TProtocol { private _lastField : Array < any > ; private _lastFieldId : number ; private _booleanField : any ; private _boolValue : any ; constructor ( trans , logger = defaultLogger ) { super ( trans , logger ) ; this . _lastField = [ ] ; this . _lastFieldId = 0 ; this . _booleanField = { name : null , hasBoolValue : false , } ; this . _boolValue = { hasBoolValue : false , boolValue : false , } ; } public getCompactType ( ttype ) { return TTypeToCType [ ttype ] ; } public getTType = function ( type ) : TType { switch ( type ) { case CompactType . STOP : return TType . STOP ; case CompactType . BOOLEAN_FALSE : case CompactType . BOOLEAN_TRUE : return TType . BOOL ; case CompactType . BYTE : return TType . BYTE ; case CompactType . I16 : return TType . I16 ; case CompactType . I32 : return TType . I32 ; case CompactType . I64 : return TType . I64 ; case CompactType . DOUBLE : return TType . DOUBLE ; case CompactType . BINARY : return TType . STRING ; case CompactType . LIST : return TType . LIST ; case CompactType . SET : return TType . SET ; case CompactType . MAP : return TType . MAP ; case CompactType . STRUCT : return TType . STRUCT ; default : throw new TProtocolException ( TProtocolExceptionType . INVALID_DATA , `Unknown type: ${ type }` ) ; } } ; public writeMessageBegin ( name , type , requestId ) { this . writeByte ( PROTOCOL_ID ) ; this . writeByte ( ( VERSION_N & VERSION_MASK ) | ( ( type << TYPE_SHIFT_AMOUNT ) & TYPE_MASK ) ) ; this . writeVarint32 ( requestId ) ; this . writeString ( name ) ; if ( this . requestId ) { this . logger ( [ 'warn' , 'CompactProtocol' ] , `requestId already set: ${ name }` ) ; } else { this . requestId = requestId ; } } public writeMessageEnd ( ) { if ( this . requestId !== null ) { this . requestId = null ; } else { this . logger ( [ 'warn' , 'CompactProtocol' ] , 'No requestId to unset' ) ; } } public writeStructBegin ( name ) { this . _lastField . push ( this . _lastFieldId ) ; this . _lastFieldId = 0 ; } public writeStructEnd ( ) { this . _lastFieldId = this . _lastField . pop ( ) ; } param { string } name param { number } type param { number } id public writeFieldBegin ( name , type , id ) { if ( type !== TType . BOOL ) { return this . writeFieldBeginInternal ( name , type , id , - 1 ) ; } else { this . _booleanField . name = name ; this . _booleanField . fieldType = type ; this . _booleanField . fieldId = id ; } } public writeFieldEnd ( ) { } public writeFieldStop ( ) { this . writeByte ( CompactType . STOP ) ; } public writeMapBegin ( keyType , valueType , size ) { if ( size === 0 ) { this . writeByte ( 0 ) ; } else { this . writeVarint32 ( size ) ; this . writeByte ( ( this . getCompactType ( keyType ) << 4 ) | this . getCompactType ( valueType ) ) ; } } public writeMapEnd ( ) { } public writeListBegin ( elementType , size ) { this . writeCollectionBegin ( elementType , size ) ; } public writeListEnd ( ) { } public writeSetBegin ( elementType , size ) { this . writeCollectionBegin ( elementType , size ) ; } public writeSetEnd ( ) { } public writeBool ( bool ) { if ( this . _booleanField . name !== null ) { this . writeFieldBeginInternal ( this . _booleanField . name , this . _booleanField . fieldType , this . _booleanField . fieldId , bool ? CompactType . BOOLEAN_TRUE : CompactType . BOOLEAN_FALSE ) ; this . _booleanField . name = null ; } else { this . writeByte ( bool ? CompactType . BOOLEAN_TRUE : CompactType . BOOLEAN_FALSE ) ; } } public writeByte ( byte ) { this . transport . write ( Buffer . from ( [ byte ] ) ) ; } public writeI16 ( i16 ) { this . writeVarint32 ( this . i32ToZigzag ( i16 ) ) ; } public writeI32 ( i32 ) { this . writeVarint32 ( this . i32ToZigzag ( i32 ) ) ; } public writeI64 ( i64 ) { this . writeVarint64 ( this . i64ToZigzag ( i64 ) ) ; } public writeDouble ( dub ) { const buff = Buffer . alloc ( 8 ) ; this . transport . write ( writeDoubleLE ( buff , dub ) ) ; } public writeStringOrBinary ( name , encoding , data ) { if ( typeof data === 'string' ) { this . writeVarint32 ( Buffer . byteLength ( data , encoding ) ) ; this . transport . write ( Buffer . from ( data , encoding ) ) ; } else if ( data instanceof Buffer || Object . prototype . toString . call ( data ) === '[object Buffer]' ) { this . writeVarint32 ( data . length ) ; this . transport . write ( data ) ; } else { throw new Error ( `${ name } called without a string/Buffer argument: ${ data }` ) ; } } public writeString ( data ) { this . writeStringOrBinary ( 'writeString' , 'utf8' , data ) ; } public writeBinary ( data ) { this . writeStringOrBinary ( 'writeBinary' , 'binary' , data ) ; } public readMessageBegin ( ) { const protocolId = this . transport . readByte ( ) ; if ( protocolId !== PROTOCOL_ID ) { throw new TProtocolException ( TProtocolExceptionType . BAD_VERSION , `Bad protocol identifier ${ protocolId }` ) ; } else { const versionAndType = this . transport . readByte ( ) ; const version = versionAndType & VERSION_MASK ; if ( version !== VERSION_N ) { throw new TProtocolException ( TProtocolExceptionType . BAD_VERSION , `Bad protocol version ${ version }` ) ; } const messageType = ( versionAndType >> TYPE_SHIFT_AMOUNT ) & TYPE_BITS ; const requestId = this . readVarint32 ( ) ; const fieldName = this . readString ( ) ; return { fieldName , messageType , requestId , } ; } } public readMessageEnd ( ) { } public readStructBegin ( ) { this . _lastField . push ( this . _lastFieldId ) ; this . _lastFieldId = 0 ; return { fieldName : '' } ; } public readStructEnd ( ) { this . _lastFieldId = this . _lastField . pop ( ) ; } public readFieldBegin ( ) { let fieldId = 0 ; const byte = this . transport . readByte ( ) ; const type = byte & 0x0f ; if ( type === CompactType . STOP ) { return { fieldName : '' , fieldType : TType . STOP , fieldId , } ; } else { const modifier = ( byte & 0x000000f0 ) >>> 4 ; if ( modifier === 0 ) { fieldId = this . readI16 ( ) ; } else { fieldId = this . _lastFieldId + modifier ; } const fieldType = this . getTType ( type ) ; if ( type === CompactType . BOOLEAN_TRUE || type === CompactType . BOOLEAN_FALSE ) { this . _boolValue . hasBoolValue = true ; this . _boolValue . boolValue = type === CompactType . BOOLEAN_TRUE ? true : false ; } this . _lastFieldId = fieldId ; return { fieldName : '' , fieldType , fieldId , } ; } } public readFieldEnd ( ) { } public readMapBegin ( ) { const size = this . readVarint32 ( ) ; if ( size < 0 ) { throw new TProtocolException ( TProtocolExceptionType . NEGATIVE_SIZE , `Negative map size` ) ; } else { let kvType = 0 ; if ( size !== 0 ) { kvType = this . transport . readByte ( ) ; } const keyType = this . getTType ( ( kvType & 0xf0 ) >>> 4 ) ; const valueType = this . getTType ( kvType & 0xf ) ; return { keyType , valueType , size } ; } } public readMapEnd ( ) { } public readListBegin ( ) { const sizeType = this . transport . readByte ( ) ; let size = ( sizeType >>> 4 ) & 0x0000000f ; if ( size === 15 ) { size = this . readVarint32 ( ) ; } if ( size < 0 ) { throw new TProtocolException ( TProtocolExceptionType . NEGATIVE_SIZE , `Negative list size` ) ; } else { const elementType = this . getTType ( sizeType & 0x0000000f ) ; return { elementType , size } ; } } public readListEnd ( ) { } public readSetBegin ( ) { return this . readListBegin ( ) ; } public readSetEnd ( ) { } public readBool ( ) { let value = false ; if ( this . _boolValue . hasBoolValue === true ) { value = this . _boolValue . boolValue ; this . _boolValue . hasBoolValue = false ; } else { const res = this . transport . readByte ( ) ; value = res === CompactType . BOOLEAN_TRUE ; } return value ; } public readByte ( ) { return this . transport . readByte ( ) ; } public readI16 ( ) { return this . readI32 ( ) ; } public readI32 ( ) { return this . zigzagToI32 ( this . readVarint32 ( ) ) ; } public readI64 ( ) { return this . zigzagToI64 ( this . readVarint64 ( ) ) ; } public readDouble ( ) { const buff = this . transport . read ( 8 ) ; return readDoubleLE ( buff ) ; } public readBinary ( ) { const size = this . readVarint32 ( ) ; if ( size === 0 ) { return Buffer . alloc ( 0 ) ; } if ( size < 0 ) { throw new TProtocolException ( TProtocolExceptionType . NEGATIVE_SIZE , `Negative binary size` ) ; } else { return this . transport . read ( size ) ; } } public readString ( ) { const size = this . readVarint32 ( ) ; if ( size === 0 ) { return '' ; } else { if ( size < 0 ) { throw new TProtocolException ( TProtocolExceptionType . NEGATIVE_SIZE , `Negative string size` ) ; } return this . transport . readString ( size ) ; } } public skip ( type ) { switch ( type ) { case TType . STOP : return ; case TType . BOOL : this . readBool ( ) ; break ; case TType . BYTE : this . readByte ( ) ; break ; case TType . I16 : this . readI16 ( ) ; break ; case TType . I32 : this . readI32 ( ) ; break ; case TType . I64 : this . readI64 ( ) ; break ; case TType . DOUBLE : this . readDouble ( ) ; break ; case TType . STRING : this . readString ( ) ; break ; case TType . STRUCT : this . readStructBegin ( ) ; while ( true ) { const fieldBegin = this . readFieldBegin ( ) ; if ( fieldBegin . fieldType === TType . STOP ) { break ; } this . skip ( fieldBegin . fieldType ) ; this . readFieldEnd ( ) ; } this . readStructEnd ( ) ; break ; case TType . MAP : const mapBegin = this . readMapBegin ( ) ; for ( let i = 0 ; i < mapBegin . size ; ++ i ) { this . skip ( mapBegin . keyType ) ; this . skip ( mapBegin . valueType ) ; } this . readMapEnd ( ) ; break ; case TType . SET : const setBegin = this . readSetBegin ( ) ; for ( let i2 = 0 ; i2 < setBegin . size ; ++ i2 ) { this . skip ( setBegin . elementType ) ; } this . readSetEnd ( ) ; break ; case TType . LIST : const listBegin = this . readListBegin ( ) ; for ( let i3 = 0 ; i3 < listBegin . size ; ++ i3 ) { this . skip ( listBegin . elementType ) ; } this . readListEnd ( ) ; break ; default : throw new Error ( 'Invalid type: ' + type ) ; } } /**
     * Send any buffered bytes to the end point.
     */ public flush ( ) { return this . transport . flush ( ) ; } /**
     * Convert from zigzag int to int.
     */ private zigzagToI32 ( i32 ) { return ( i32 >>> 1 ) ^ ( - 1 * ( i32 & 1 ) ) ; } /**
     * Convert from zigzag long to long.
     */ private zigzagToI64 ( i64 ) { let hi = i64 . buffer . readUInt32BE ( 0 , true ) ; let lo = i64 . buffer . readUInt32BE ( 4 , true ) ; const lowBit = i64 . buffer . readUInt8 ( 7 , true ) & 0x01 ; const mask = lowBit ? 0xffffffff : 0 ; const hiLo = hi << 31 ; hi = ( hi >>> 1 ) ^ mask ; lo = ( ( lo >>> 1 ) | hiLo ) ^ mask ; return new Int64 ( hi , lo ) ; } /**
     * Read an i32 from the wire as a varint. The MSB of each byte is set
     * if there is another byte to follow. This can read up to 5 bytes.
     */ private readVarint32 ( ) { return this . readVarint64 ( ) . toNumber ( ) ; } /**
     * Read an i64 from the wire as a proper varint. The MSB of each byte is set
     * if there is another byte to follow. This can read up to 10 bytes.
     */ private readVarint64 ( ) { let rsize = 0 ; let lo = 0 ; let hi = 0 ; let shift = 0 ; while ( true ) { const b = this . transport . readByte ( ) ; rsize ++ ; if ( shift <= 25 ) { lo = lo | ( ( b & 0x7f ) << shift ) ; } else if ( 25 < shift && shift < 32 ) { lo = lo | ( ( b & 0x7f ) << shift ) ; hi = hi | ( ( b & 0x7f ) >>> ( 32 - shift ) ) ; } else { hi = hi | ( ( b & 0x7f ) << ( shift - 32 ) ) ; } shift += 7 ; if ( ! ( b & 0x80 ) ) { break ; } if ( rsize >= 10 ) { throw new TProtocolException ( TProtocolExceptionType . INVALID_DATA , `Variable-length int over 10 bytes.` ) ; } } return new Int64 ( hi , lo ) ; } private writeFieldBeginInternal ( name , fieldType , fieldId , typeOverride ) { const typeToWrite = typeOverride === - 1 ? this . getCompactType ( fieldType ) : typeOverride ; if ( fieldId > this . _lastFieldId && fieldId - this . _lastFieldId <= 15 ) { this . writeByte ( ( ( fieldId - this . _lastFieldId ) << 4 ) | typeToWrite ) ; } else { this . writeByte ( typeToWrite ) ; this . writeI16 ( fieldId ) ; } this . _lastFieldId = fieldId ; } private writeCollectionBegin ( elementType , size ) { if ( size <= 14 ) { this . writeByte ( ( size << 4 ) | this . getCompactType ( elementType ) ) ; } else { this . writeByte ( 0xf0 | this . getCompactType ( elementType ) ) ; this . writeVarint32 ( size ) ; } } /**
     * Write an i32 as a varint. Results in 1-5 bytes on the wire.
     */ private writeVarint32 ( i32 ) { const buf = Buffer . alloc ( 5 ) ; let wsize = 0 ; while ( true ) { if ( ( i32 & ~ 0x7f ) === 0 ) { buf [ wsize ++ ] = i32 ; break ; } else { buf [ wsize ++ ] = ( i32 & 0x7f ) | 0x80 ; i32 = i32 >>> 7 ; } } const wbuf = Buffer . alloc ( wsize ) ; buf . copy ( wbuf , 0 , 0 , wsize ) ; this . transport . write ( wbuf ) ; } /**
     * Write an i64 as a varint. Results in 1-10 bytes on the wire.
     * N.B. node-int64 is always big endian
     */ private writeVarint64 ( i64 ) { if ( typeof i64 === 'number' ) { i64 = new Int64 ( i64 ) ; } if ( ! ( i64 instanceof Int64 ) ) { throw new TProtocolException ( TProtocolExceptionType . INVALID_DATA , `Expected Int64 or Number, found: ${ i64 }` ) ; } else { const buf = Buffer . alloc ( 10 ) ; let wsize = 0 ; let hi = i64 . buffer . readUInt32BE ( 0 , true ) ; let lo = i64 . buffer . readUInt32BE ( 4 , true ) ; let mask = 0 ; while ( true ) { if ( ( lo & ~ 0x7f ) === 0 && hi === 0 ) { buf [ wsize ++ ] = lo ; break ; } else { buf [ wsize ++ ] = ( lo & 0x7f ) | 0x80 ; mask = hi << 25 ; lo = lo >>> 7 ; hi = hi >>> 7 ; lo = lo | mask ; } } const wbuf = Buffer . alloc ( wsize ) ; buf . copy ( wbuf , 0 , 0 , wsize ) ; this . transport . write ( wbuf ) ; } } /**
     * Convert l into a zigzag long. This allows negative numbers to be
     * represented compactly as a varint.
     */ private i64ToZigzag ( i64 ) { if ( typeof i64 === 'string' ) { i64 = Int64 . fromDecimalString ( i64 ) ; } else if ( typeof i64 === 'number' ) { i64 = new Int64 ( i64 ) ; } if ( ! isInt64 ( i64 ) ) { throw new TProtocolException ( TProtocolExceptionType . INVALID_DATA , `Expected Int64, number, or decimal string but found type ${ typeof i64 }` ) ; } else { let hi = i64 . buffer . readUInt32BE ( 0 , true ) ; let lo = i64 . buffer . readUInt32BE ( 4 , true ) ; const sign = hi >>> 31 ; hi = ( ( hi << 1 ) | ( lo >>> 31 ) ) ^ ( ! ! sign ? 0xffffffff : 0 ) ; lo = ( lo << 1 ) ^ ( ! ! sign ? 0xffffffff : 0 ) ; return new Int64 ( hi , lo ) ; } } /**
     * Convert n into a zigzag int. This allows negative numbers to be
     * represented compactly as a varint.
     */ private i32ToZigzag ( i32 ) { return ( i32 << 1 ) ^ ( i32 & 0x80000000 ? 0xffffffff : 0 ) ; } }