'use strict' ; import { inspect } from 'util' ; import { BufferVisitor , readUFloat16 , writeUFloat16 } from './common' ; import { QuicError , QUICError , QUICStreamError } from './error' ; import { PacketNumber , Offset , StreamID } from './protocol' ; export function isCongestionType ( flag ) { return ( flag & 0b11100000 ) === 0b00100000 ; } export function isACKType ( flag ) { return ( flag & 0b11000000 ) === 0b01000000 ; } export function isStreamType ( flag ) { return flag > 0b10000000 ; } export function parseFrame ( bufv , headerPacketNumber ) { bufv . walk ( 0 ) ; const type = bufv . buf . readUInt8 ( bufv . start ) ; if ( type >= 128 ) { return StreamFrame . fromBuffer ( bufv ) ; } if ( type >= 64 ) { return AckFrame . fromBuffer ( bufv ) ; } if ( type >= 32 ) { return CongestionFeedbackFrame . fromBuffer ( bufv ) ; } switch ( type ) { case 0 : return PaddingFrame . fromBuffer ( bufv ) ; case 1 : return RstStreamFrame . fromBuffer ( bufv ) ; case 2 : return ConnectionCloseFrame . fromBuffer ( bufv ) ; case 3 : return GoAwayFrame . fromBuffer ( bufv ) ; case 4 : return WindowUpdateFrame . fromBuffer ( bufv ) ; case 5 : return BlockedFrame . fromBuffer ( bufv ) ; case 6 : return StopWaitingFrame . fromBuffer ( bufv , headerPacketNumber ) ; case 7 : return PingFrame . fromBuffer ( bufv ) ; default : throw new QuicError ( 'QUIC_INVALID_FRAME_DATA' ) ; } } /** Frame representing a QUIC frame. */ export abstract class Frame { static fromBuffer ( _bufv , _headerPacketNumber ? ) { throw new Error ( `class method "fromBuffer" is not implemented` ) ; } type : number ; name : string ; constructor ( type , name ) { this . type = type ; this . name = name ; } valueOf ( ) { return { name : this . name , type : this . type , } ; } toString ( ) { return JSON . stringify ( this . valueOf ( ) ) ; } isRetransmittable ( ) { return this . name !== 'ACK' && this . name !== 'STOP_WAITING' ; } [ inspect . custom ] ( _depth , _options ) { return `<${ this . constructor . name } ${ this . toString ( ) }>` ; } abstract byteLen ( ) ; abstract writeTo ( bufv ) ; } /** StreamFrame representing a QUIC STREAM frame. */ export class StreamFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( ! isStreamType ( type ) ) { throw new QuicError ( 'QUIC_INVALID_STREAM_DATA' ) ; } const isFIN = ( type & 0b1000000 ) > 0 ; const streamID = StreamID . fromBuffer ( bufv , StreamID . flagToByteLen ( type & 0b11 ) ) ; const offset = Offset . fromBuffer ( bufv , Offset . flagToByteLen ( ( type & 0b11100 ) >> 2 ) ) ; let data = null ; if ( ( type & 0b100000 ) > 0 ) { bufv . mustWalk ( 2 , 'QUIC_INVALID_STREAM_DATA' ) ; const len = bufv . buf . readUInt16BE ( bufv . start ) ; if ( len > 0 ) { bufv . mustWalk ( len , 'QUIC_INVALID_STREAM_DATA' ) ; data = Buffer . allocUnsafe ( len ) ; bufv . buf . copy ( data , 0 , bufv . start , bufv . end ) ; } } else if ( bufv . length > bufv . end ) { bufv . walk ( bufv . length - bufv . end ) ; data = Buffer . allocUnsafe ( bufv . end - bufv . start ) ; bufv . buf . copy ( data , 0 , bufv . start , bufv . end ) ; } const frame = new StreamFrame ( streamID , offset , isFIN ) ; frame . setData ( data ) ; frame . type = type ; return frame ; } streamID : StreamID ; offset : Offset ; isFIN : boolean ; data : Buffer | null ; constructor ( streamID , offset , isFIN = false ) { super ( 0b10000000 , 'STREAM' ) ; this . streamID = streamID ; this . offset = offset ; this . isFIN = isFIN ; this . data = null ; } setData ( data ) { if ( data != null && data . length === 0 ) { data = null ; } if ( data == null ) { this . isFIN = true ; } this . data = data ; return this ; } valueOf ( ) { return { name : this . name , type : this . type , isFIN : this . isFIN , streamID : this . streamID . valueOf ( ) , offset : this . offset . valueOf ( ) , data : this . data , } ; } headerLen ( hasDataLen ) { const len = hasDataLen ? 2 : 0 ; return 1 + this . streamID . byteLen ( ) + this . offset . byteLen ( ) + len ; } byteLen ( ) { const dataLen = this . data != null ? this . data . length : 0 ; return this . headerLen ( dataLen > 0 ) + dataLen ; } writeTo ( bufv ) { if ( this . isFIN ) { this . type |= 0b1000000 ; } if ( this . data != null ) { this . type |= 0b00100000 ; } this . type |= this . offset . flagBits ( ) << 2 ; this . type |= this . streamID . flagBits ( ) ; bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . streamID . writeTo ( bufv ) ; this . offset . writeTo ( bufv ) ; if ( this . data != null ) { bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( this . data . length , bufv . start ) ; bufv . walk ( this . data . length ) ; this . data . copy ( bufv . buf , bufv . start , 0 , this . data . length ) ; } return bufv ; } } /** AckRange representing a range for ACK. */ export class AckRange { last : number ; first : number ; constructor ( firstPacketNumberValue , lastPacketNumberValue ) { this . last = lastPacketNumberValue ; this . first = firstPacketNumberValue ; } len ( ) { return this . last - this . first + 1 ; } } /** AckFrame representing a QUIC ACK frame. */ export class AckFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( ! isACKType ( type ) ) { throw new QuicError ( 'QUIC_INVALID_ACK_DATA' ) ; } const frame = new AckFrame ( ) ; const hasMissingRanges = ( type & 0b00100000 ) > 0 ; const missingNumberDeltaLen = PacketNumber . flagToByteLen ( type & 0b11 ) ; const largestAckedNumber = PacketNumber . fromBuffer ( bufv , PacketNumber . flagToByteLen ( ( type >> 2 ) & 0b11 ) ) ; frame . largestAcked = largestAckedNumber . valueOf ( ) ; bufv . mustWalk ( 2 , 'QUIC_INVALID_ACK_DATA' ) ; frame . delayTime = readUFloat16 ( bufv . buf , bufv . start ) ; let numAckBlocks = 0 ; if ( hasMissingRanges ) { bufv . mustWalk ( 1 , 'QUIC_INVALID_ACK_DATA' ) ; numAckBlocks = bufv . buf . readUInt8 ( bufv . start ) ; } if ( hasMissingRanges && numAckBlocks === 0 ) { throw new QuicError ( 'QUIC_INVALID_ACK_DATA' ) ; } bufv . mustWalk ( missingNumberDeltaLen , 'QUIC_INVALID_ACK_DATA' ) ; let ackBlockLength = bufv . buf . readUIntBE ( bufv . start , missingNumberDeltaLen ) ; if ( ( frame . largestAcked > 0 && ackBlockLength < 1 ) || ackBlockLength > frame . largestAcked ) { throw new QuicError ( 'QUIC_INVALID_ACK_DATA' ) ; } if ( hasMissingRanges ) { let ackRange = new AckRange ( frame . largestAcked - ackBlockLength + 1 , frame . largestAcked ) ; frame . ackRanges . push ( ackRange ) ; let inLongBlock = false ; let lastRangeComplete = false ; for ( let i = 0 ; i < numAckBlocks ; i ++ ) { bufv . mustWalk ( 1 , 'QUIC_INVALID_ACK_DATA' ) ; const gap = bufv . buf . readUInt8 ( bufv . start ) ; bufv . mustWalk ( missingNumberDeltaLen , 'QUIC_INVALID_ACK_DATA' ) ; ackBlockLength = bufv . buf . readUIntBE ( bufv . start , missingNumberDeltaLen ) ; const lastAckRange = frame . ackRanges [ frame . ackRanges . length - 1 ] ; if ( inLongBlock ) { lastAckRange . first -= gap + ackBlockLength ; lastAckRange . last -= gap ; } else { lastRangeComplete = false ; ackRange = new AckRange ( 0 , lastAckRange . first - gap - 1 ) ; ackRange . first = ackRange . last - ackBlockLength + 1 ; frame . ackRanges . push ( ackRange ) ; } if ( ackBlockLength > 0 ) { lastRangeComplete = true ; } inLongBlock = ( ackBlockLength === 0 ) ; } if ( ! lastRangeComplete ) { frame . ackRanges = frame . ackRanges . slice ( 0 , - 1 ) ; } frame . lowestAcked = frame . ackRanges [ frame . ackRanges . length - 1 ] . first ; } else { if ( frame . largestAcked === 0 ) { frame . lowestAcked = 0 ; } else { frame . lowestAcked = frame . largestAcked - ackBlockLength + 1 ; } } if ( ! frame . validateAckRanges ( ) ) { throw new QuicError ( 'QUIC_INVALID_ACK_DATA' ) ; } bufv . mustWalk ( 1 , 'QUIC_INVALID_ACK_DATA' ) ; const numTimestamp = bufv . buf . readUInt8 ( bufv . start ) ; if ( numTimestamp > 0 ) { bufv . mustWalk ( 1 , 'QUIC_INVALID_ACK_DATA' ) ; bufv . mustWalk ( 4 , 'QUIC_INVALID_ACK_DATA' ) ; for ( let i = 0 ; i < numTimestamp - 1 ; i ++ ) { bufv . mustWalk ( 1 , 'QUIC_INVALID_ACK_DATA' ) ; bufv . mustWalk ( 2 , 'QUIC_INVALID_ACK_DATA' ) ; } } return frame ; } largestAcked : number ; lowestAcked : number ; ackRanges : AckRange [ ] ; delayTime : number ; largestAckedTime : number ; constructor ( ) { super ( 0b01000000 , 'ACK' ) ; this . largestAcked = 0 ; this . lowestAcked = 0 ; this . ackRanges = [ ] ; this . delayTime = 0 ; this . largestAckedTime = 0 ; } valueOf ( ) { return { name : this . name , type : this . type , largestAcked : this . largestAcked , lowestAcked : this . lowestAcked , delayTime : this . delayTime , ackRanges : this . ackRanges , } ; } hasMissingRanges ( ) { return this . ackRanges . length > 0 ; } validateAckRanges ( ) { if ( this . ackRanges . length === 0 ) { return true ; } if ( this . ackRanges . length === 1 ) { return false ; } if ( this . ackRanges [ 0 ] . last !== this . largestAcked ) { return false ; } for ( const ackRange of this . ackRanges ) { if ( ackRange . first > ackRange . last || ackRange . first <= 0 ) { return false ; } } for ( let i = 1 , l = this . ackRanges . length ; i < l ; i ++ ) { const lastAckRange = this . ackRanges [ i - 1 ] ; if ( lastAckRange . first <= this . ackRanges [ i ] . first ) { return false ; } if ( lastAckRange . first <= ( this . ackRanges [ i ] . last + 1 ) ) { return false ; } } return true ; } numWritableNackRanges ( ) { if ( this . ackRanges . length === 0 ) { return 0 ; } let numRanges = 0 ; for ( let i = 1 , l = this . ackRanges . length ; i < l ; i ++ ) { const lastAckRange = this . ackRanges [ i - 1 ] ; const gap = lastAckRange . first - this . ackRanges [ i ] . last - 1 ; let rangeLength = 1 + Math . floor ( gap / 0xff ) ; if ( gap % 0xff === 0 ) { rangeLength -- ; } if ( numRanges + rangeLength < 0xff ) { numRanges += rangeLength ; } else { break ; } } return numRanges + 1 ; } getMissingNumberDeltaFlagBits ( ) { let maxRangeLength = 0 ; if ( this . hasMissingRanges ( ) ) { for ( const ackRange of this . ackRanges ) { const rangeLength = ackRange . len ( ) ; if ( rangeLength > maxRangeLength ) { maxRangeLength = rangeLength ; } } } else { maxRangeLength = this . largestAcked - this . lowestAcked + 1 ; } if ( maxRangeLength <= 0xff ) { return 0 ; } if ( maxRangeLength <= 0xffff ) { return 1 ; } if ( maxRangeLength <= 0xffffff ) { return 2 ; } return 3 ; } setDelay ( ) { this . delayTime = ( Date . now ( ) - this . largestAckedTime ) * 1000 ; } acksPacket ( val ) { if ( val < this . lowestAcked || val > this . largestAcked ) { return false ; } if ( this . hasMissingRanges ( ) ) { for ( const ackRange of this . ackRanges ) { if ( val >= ackRange . first && val <= ackRange . last ) { return true ; } } return false ; } return ( val >= this . lowestAcked && val <= this . largestAcked ) ; } byteLen ( ) { const hasMissingRanges = this . hasMissingRanges ( ) ; const largestAckedNum = new PacketNumber ( this . largestAcked ) ; const flagBits = this . getMissingNumberDeltaFlagBits ( ) ; const largestAckedLen = largestAckedNum . byteLen ( ) ; const missingNumberDeltaLen = PacketNumber . flagToByteLen ( flagBits ) ; let frameLen = 1 + largestAckedLen + 2 ; let numRanges = 0 ; if ( ! hasMissingRanges ) { frameLen += missingNumberDeltaLen ; } else { numRanges = this . numWritableNackRanges ( ) ; if ( numRanges > 0xff ) { throw new Error ( 'AckFrame: Too many ACK ranges' ) ; } frameLen += missingNumberDeltaLen + 1 ; frameLen += ( missingNumberDeltaLen + 1 ) * ( numRanges - 1 ) ; } return frameLen + 1 ; } writeTo ( bufv ) { const hasMissingRanges = this . hasMissingRanges ( ) ; if ( hasMissingRanges ) { this . type |= 0b100000 ; } const largestAckedNum = new PacketNumber ( this . largestAcked ) ; this . type |= largestAckedNum . flagBits ( ) << 2 ; const flagBits = this . getMissingNumberDeltaFlagBits ( ) ; this . type |= flagBits ; const missingNumberDeltaLen = PacketNumber . flagToByteLen ( flagBits ) ; let numRanges = 0 ; bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; largestAckedNum . writeTo ( bufv ) ; bufv . walk ( 2 ) ; writeUFloat16 ( bufv . buf , this . delayTime , bufv . start ) ; let numRangesWritten = 0 ; if ( hasMissingRanges ) { numRanges = this . numWritableNackRanges ( ) ; if ( numRanges > 0xff ) { throw new QuicError ( 'AckFrame: Too many ACK ranges' ) ; } bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( numRanges - 1 , bufv . start ) ; } let firstAckBlockLength = 0 ; if ( ! hasMissingRanges ) { firstAckBlockLength = this . largestAcked - this . lowestAcked + 1 ; } else { if ( this . largestAcked !== this . ackRanges [ 0 ] . last ) { throw new QuicError ( 'AckFrame: largestAcked does not match ACK ranges' ) ; } if ( this . lowestAcked !== this . ackRanges [ this . ackRanges . length - 1 ] . first ) { throw new QuicError ( 'AckFrame: lowestAcked does not match ACK ranges' ) ; } firstAckBlockLength = this . largestAcked - this . ackRanges [ 0 ] . first + 1 ; numRangesWritten ++ ; } bufv . walk ( missingNumberDeltaLen ) ; bufv . buf . writeUIntBE ( firstAckBlockLength , bufv . start , missingNumberDeltaLen ) ; for ( let i = 1 , l = this . ackRanges . length ; i < l ; i ++ ) { const length = this . ackRanges [ i ] . len ( ) ; const gap = this . ackRanges [ i - 1 ] . first - this . ackRanges [ i ] . last - 1 ; let num = Math . floor ( gap / 0xff ) + 1 ; if ( gap % 0xff === 0 ) { num -- ; } if ( num === 1 ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( gap , bufv . start ) ; bufv . walk ( missingNumberDeltaLen ) ; bufv . buf . writeUIntBE ( length , bufv . start , missingNumberDeltaLen ) ; numRangesWritten ++ ; } else { for ( let j = 0 ; j < num ; j ++ ) { let lengthWritten = 0 ; let gapWritten = 0 ; if ( j === num - 1 ) { lengthWritten = length ; gapWritten = 1 + ( ( gap - 1 ) % 255 ) ; } else { lengthWritten = 0 ; gapWritten = 0xff ; } bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( gapWritten , bufv . start ) ; bufv . walk ( missingNumberDeltaLen ) ; bufv . buf . writeUIntBE ( lengthWritten , bufv . start , missingNumberDeltaLen ) ; numRangesWritten ++ ; } } if ( numRangesWritten >= numRanges ) { break ; } } if ( numRanges !== numRangesWritten ) { throw new QuicError ( 'AckFrame: Inconsistent number of ACK ranges written' ) ; } bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( 0 , bufv . start ) ; return bufv ; } } /** StopWaitingFrame representing a QUIC STOP_WAITING frame. */ export class StopWaitingFrame extends Frame { static fromBuffer ( bufv , packetNumber ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x06 ) { throw new QuicError ( 'QUIC_INVALID_STOP_WAITING_DATA' ) ; } const len = packetNumber . byteLen ( ) ; bufv . mustWalk ( len , 'QUIC_INVALID_STOP_WAITING_DATA' ) ; const delta = bufv . buf . readIntBE ( bufv . start , len , false ) ; return new StopWaitingFrame ( packetNumber , packetNumber . valueOf ( ) - delta ) ; } packetNumber : PacketNumber ; leastUnacked : number ; constructor ( packetNumber , leastUnacked ) { super ( 0x06 , 'STOP_WAITING' ) ; this . packetNumber = packetNumber ; this . leastUnacked = leastUnacked ; } valueOf ( ) { return { name : this . name , type : this . type , packetNumber : this . packetNumber . valueOf ( ) , leastUnacked : this . leastUnacked , } ; } byteLen ( ) { return 1 + this . packetNumber . byteLen ( ) ; } writeTo ( bufv ) { const len = this . packetNumber . byteLen ( ) ; bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; bufv . walk ( len ) ; bufv . buf . writeUIntBE ( this . packetNumber . valueOf ( ) - this . leastUnacked , bufv . start , len ) ; return bufv ; } } /** WindowUpdateFrame representing a QUIC WINDOW_UPDATE frame. */ export class WindowUpdateFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x04 ) { throw new QuicError ( 'QUIC_INVALID_WINDOW_UPDATE_DATA' ) ; } const streamID = StreamID . fromBuffer ( bufv , 4 ) ; const offset = Offset . fromBuffer ( bufv , 8 ) ; return new WindowUpdateFrame ( streamID , offset ) ; } streamID : StreamID ; offset : Offset ; constructor ( streamID , offset ) { super ( 0x04 , 'WINDOW_UPDATE' ) ; this . streamID = streamID ; this . offset = offset ; } valueOf ( ) { return { name : this . name , type : this . type , streamID : this . streamID . valueOf ( ) , offset : this . offset . valueOf ( ) , } ; } byteLen ( ) { return 13 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . streamID . writeTo ( bufv , true ) ; this . offset . writeTo ( bufv , true ) ; return bufv ; } } /** BlockedFrame representing a QUIC BLOCKED frame. */ export class BlockedFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x05 ) { throw new QuicError ( 'QUIC_INVALID_BLOCKED_DATA' ) ; } const streamID = StreamID . fromBuffer ( bufv , 4 ) ; return new BlockedFrame ( streamID ) ; } streamID : StreamID ; constructor ( streamID ) { super ( 0x05 , 'BLOCKED' ) ; this . streamID = streamID ; } valueOf ( ) { return { name : this . name , type : this . type , streamID : this . streamID . valueOf ( ) , } ; } byteLen ( ) { return 5 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . streamID . writeTo ( bufv , true ) ; return bufv ; } } /** CongestionFeedbackFrame representing a QUIC CONGESTION_FEEDBACK frame. */ export class CongestionFeedbackFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( ! isCongestionType ( type ) ) { throw new QuicError ( 'QUIC_INVALID_FRAME_DATA' ) ; } return new CongestionFeedbackFrame ( ) ; } constructor ( ) { super ( 0b00100000 , 'CONGESTION_FEEDBACK' ) ; } byteLen ( ) { return 1 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; return bufv ; } } /** PaddingFrame representing a QUIC PADDING frame. */ export class PaddingFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type > 0 ) { throw new QuicError ( 'QUIC_INVALID_FRAME_DATA' ) ; } return new PaddingFrame ( ) ; } constructor ( ) { super ( 0x00 , 'PADDING' ) ; } byteLen ( ) { return 1 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( 0 , bufv . start ) ; return bufv ; } } /** RstStreamFrame representing a QUIC RST_STREAM frame. */ export class RstStreamFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x01 || bufv . length < ( bufv . end + 16 ) ) { throw new QuicError ( 'QUIC_INVALID_RST_STREAM_DATA' ) ; } const streamID = StreamID . fromBuffer ( bufv , 4 ) ; const offset = Offset . fromBuffer ( bufv , 8 ) ; const error = QuicError . fromBuffer ( bufv ) ; return new RstStreamFrame ( streamID , offset , error ) ; } streamID : StreamID ; offset : Offset ; error : QUICStreamError ; constructor ( streamID , offset , error ) { super ( 0x01 , 'RST_STREAM' ) ; this . streamID = streamID ; this . offset = offset ; this . error = error ; } valueOf ( ) { return { name : this . name , type : this . type , streamID : this . streamID . valueOf ( ) , offset : this . offset . valueOf ( ) , error : this . error . valueOf ( ) , } ; } byteLen ( ) { return 17 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . streamID . writeTo ( bufv , true ) ; this . offset . writeTo ( bufv , true ) ; this . error . writeTo ( bufv ) ; return bufv ; } } /** PingFrame representing a QUIC PING frame. */ export class PingFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x07 ) { throw new QuicError ( 'QUIC_INVALID_FRAME_DATA' ) ; } return new PingFrame ( ) ; } constructor ( ) { super ( 0x07 , 'PING' ) ; } byteLen ( ) { return 1 ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; return bufv ; } } /** ConnectionCloseFrame representing a QUIC CONNECTION_CLOSE frame. */ export class ConnectionCloseFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x02 || bufv . length < ( bufv . end + 6 ) ) { throw new QuicError ( 'QUIC_INVALID_CONNECTION_CLOSE_DATA' ) ; } const error = QuicError . fromBuffer ( bufv ) ; bufv . walk ( 2 ) ; const reasonPhraseLen = bufv . buf . readUInt16BE ( bufv . start ) ; if ( reasonPhraseLen > 0 ) { bufv . mustWalk ( reasonPhraseLen , 'QUIC_INVALID_CONNECTION_CLOSE_DATA' ) ; error . message = bufv . buf . toString ( 'utf8' , bufv . start , bufv . end ) ; } return new ConnectionCloseFrame ( error ) ; } error : QUICError ; constructor ( error ) { super ( 0x02 , 'CONNECTION_CLOSE' ) ; this . error = error ; } valueOf ( ) { return { name : this . name , type : this . type , error : this . error . valueOf ( ) , } ; } byteLen ( ) { const reasonPhrase = this . error . message ; const reasonPhraseLen = reasonPhrase !== '' ? Buffer . byteLength ( reasonPhrase ) : 0 ; return 7 + reasonPhraseLen ; } writeTo ( bufv ) { const reasonPhrase = this . error . message ; const reasonPhraseLen = reasonPhrase !== '' ? Buffer . byteLength ( reasonPhrase ) : 0 ; bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . error . writeTo ( bufv ) ; bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( reasonPhraseLen , bufv . start ) ; if ( reasonPhrase !== '' ) { bufv . walk ( reasonPhraseLen ) ; bufv . buf . write ( reasonPhrase , bufv . start , reasonPhraseLen ) ; } return bufv ; } } /** GoAwayFrame representing a QUIC GOAWAY frame. */ export class GoAwayFrame extends Frame { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const type = bufv . buf [ bufv . start ] ; if ( type !== 0x03 ) { throw new QuicError ( 'QUIC_INVALID_GOAWAY_DATA' ) ; } const error = QuicError . fromBuffer ( bufv ) ; const streamID = StreamID . fromBuffer ( bufv , 4 ) ; bufv . mustWalk ( 2 , 'QUIC_INVALID_GOAWAY_DATA' ) ; const reasonPhraseLen = bufv . buf . readUInt16BE ( bufv . start ) ; if ( reasonPhraseLen > 0 ) { bufv . mustWalk ( reasonPhraseLen , 'QUIC_INVALID_GOAWAY_DATA' ) ; error . message = bufv . buf . toString ( 'utf8' , bufv . start , bufv . end ) ; } return new GoAwayFrame ( streamID , error ) ; } streamID : StreamID ; error : QUICError ; constructor ( lastGoodStreamID , error ) { super ( 0x03 , 'GOAWAY' ) ; this . streamID = lastGoodStreamID ; this . error = error ; } valueOf ( ) { return { name : this . name , type : this . type , streamID : this . streamID . valueOf ( ) , error : this . error . valueOf ( ) , } ; } byteLen ( ) { const reasonPhrase = this . error . message ; const reasonPhraseLen = reasonPhrase !== '' ? Buffer . byteLength ( reasonPhrase ) : 0 ; return 11 + reasonPhraseLen ; } writeTo ( bufv ) { const reasonPhrase = this . error . message ; const reasonPhraseLen = reasonPhrase !== '' ? Buffer . byteLength ( reasonPhrase ) : 0 ; bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . type , bufv . start ) ; this . error . writeTo ( bufv ) ; this . streamID . writeTo ( bufv , true ) ; bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( reasonPhraseLen , bufv . start ) ; if ( reasonPhrase !== '' ) { bufv . walk ( reasonPhraseLen ) ; bufv . buf . write ( reasonPhrase , bufv . start , reasonPhraseLen ) ; } return bufv ; } }