import { BN } from 'bn.js' ; import _ from 'lodash' ; import { common , ECPoint , InvalidFormatError , UInt160 , UInt256 } from './common' ; export class BinaryReader { public readonly buffer : Buffer ; private mutableIndex : number ; public constructor ( buffer , index = 0 ) { this . buffer = buffer ; this . mutableIndex = index ; } public get index ( ) : number { return this . mutableIndex ; } public get remaining ( ) : number { return this . buffer . length - this . mutableIndex ; } public get remainingBuffer ( ) : Buffer { return this . buffer . slice ( this . mutableIndex ) ; } public hasMore ( ) { return this . mutableIndex < this . buffer . byteLength ; } public clone ( ) { return new BinaryReader ( this . buffer , this . mutableIndex ) ; } public readBytes ( numBytes ) { this . checkRead ( numBytes ) ; const result = this . buffer . slice ( this . mutableIndex , this . mutableIndex + numBytes ) ; this . mutableIndex += numBytes ; return result ; } public readInt8 ( ) { this . checkRead ( 1 ) ; const result = this . buffer . readInt8 ( this . mutableIndex ) ; this . mutableIndex += 1 ; return result ; } public readUInt8 ( ) { this . checkRead ( 1 ) ; const result = this . buffer . readUInt8 ( this . mutableIndex ) ; this . mutableIndex += 1 ; return result ; } public readBoolean ( ) { return this . readBytes ( 1 ) [ 0 ] !== 0 ; } public readInt16LE ( ) { this . checkRead ( 2 ) ; const result = this . buffer . readInt16LE ( this . mutableIndex ) ; this . mutableIndex += 2 ; return result ; } public readUInt16LE ( ) { this . checkRead ( 2 ) ; const result = this . buffer . readUInt16LE ( this . mutableIndex ) ; this . mutableIndex += 2 ; return result ; } public readUInt16BE ( ) { this . checkRead ( 2 ) ; const result = this . buffer . readUInt16BE ( this . mutableIndex ) ; this . mutableIndex += 2 ; return result ; } public readInt32LE ( ) { this . checkRead ( 4 ) ; const result = this . buffer . readInt32LE ( this . mutableIndex ) ; this . mutableIndex += 4 ; return result ; } public readUInt32LE ( ) { this . checkRead ( 4 ) ; const result = this . buffer . readUInt32LE ( this . mutableIndex ) ; this . mutableIndex += 4 ; return result ; } public readUInt64LE ( ) { return new BN ( this . readBytes ( 8 ) , 'le' ) ; } public readInt64LE ( ) { const buffer = this . readBytes ( 8 ) ; return new BN ( buffer , 'le' ) . fromTwos ( buffer . length * 8 ) ; } public readUInt160 ( ) { return common . bufferToUInt160 ( this . readBytes ( common . UINT160_BUFFER_BYTES ) ) ; } public readUInt256 ( ) { return common . bufferToUInt256 ( this . readBytes ( common . UINT256_BUFFER_BYTES ) ) ; } public readFixed8 ( ) { return this . readInt64LE ( ) ; } public readFixedString ( length ) { const values = _ . takeWhile ( [ ... this . readBytes ( length ) ] , ( value ) => value !== 0 ) ; return Buffer . from ( values ) . toString ( 'utf8' ) ; } public readArray < T > ( read , max = 0x1000000 ) { const count = this . readVarUIntLE ( new BN ( max ) ) . toNumber ( ) ; return _ . range ( count ) . map ( read ) ; } public readObject < V > ( read , max ? ) ; public readObject < V > ( read , max ? ) ; public readObject < V > ( read , max = 0x1000000 ) { const count = this . readVarUIntLE ( new BN ( max ) ) . toNumber ( ) ; return _ . range ( count ) . reduce < { readonly [ key ] : V ; } > ( ( acc ) => { const { key , value } = read ( ) ; return { ... acc , [ key ] : value } ; } , { } ) ; } public readVarBytesLE ( max = 0x1000000 ) { return this . readBytes ( this . readVarUIntLE ( new BN ( max ) ) . toNumber ( ) ) ; } public readVarUIntLE ( maxIn = new BN ( '18446744073709551615' , 10 ) ) { const max = typeof maxIn === 'number' ? new BN ( maxIn ) : maxIn ; const fb = this . readUInt8 ( ) ; let value ; switch ( fb ) { case 0xfd : value = new BN ( this . readUInt16LE ( ) ) ; break ; case 0xfe : value = new BN ( this . readUInt32LE ( ) ) ; break ; case 0xff : value = this . readUInt64LE ( ) ; break ; default : value = new BN ( fb ) ; } if ( value . gt ( max ) ) { throw new InvalidFormatError ( `Integer too large: ${ value . toString ( 10 ) } > ${ max . toString ( 10 ) }` ) ; } return value ; } public readVarString ( max = 0x1000000 ) { return this . readVarBytesLE ( max ) . toString ( 'utf8' ) ; } public readECPoint ( ) { const firstByte = this . readBytes ( 1 ) ; if ( firstByte [ 0 ] === common . ECPOINT_INFINITY_BYTE ) { return common . ECPOINT_INFINITY ; } return common . bufferToECPoint ( Buffer . concat ( [ firstByte , this . readBytes ( common . ECPOINT_BUFFER_BYTES - 1 ) ] ) ) ; } private checkRead ( numBytes ) { if ( this . remaining < numBytes ) { throw new InvalidFormatError ( `Insufficient bytes remaining (${ this . remaining }): ${ numBytes }` ) ; } } }