'use strict' ; import { inspect } from 'util' ; import { BufferVisitor } from './common' ; import { QuicError } from './error' ; import { parseFrame , Frame } from './frame' ; import { getVersions , isSupportedVersion , ConnectionID , PacketNumber , SocketAddress , Tag , QuicTags , SessionType , } from './protocol' ; export function parsePacket ( bufv , packetSentBy ) { bufv . walk ( 0 ) ; const flag = bufv . buf . readUInt8 ( bufv . start ) ; if ( flag >= 127 ) { throw new QuicError ( 'QUIC_INTERNAL_ERROR' ) ; } if ( ( flag & 0b1000 ) === 0 ) { throw new QuicError ( 'QUIC_INTERNAL_ERROR' ) ; } if ( ( flag & 0b10 ) > 0 ) { return ResetPacket . fromBuffer ( bufv ) ; } const hasVersion = ( flag & 0b1 ) > 0 ; if ( hasVersion && packetSentBy === SessionType . SERVER ) { return NegotiationPacket . fromBuffer ( bufv ) ; } return RegularPacket . fromBuffer ( bufv , flag , packetSentBy ) ; } /** Packet representing a base Packet. */ export abstract class Packet { static fromBuffer ( _bufv , _flag ? , _packetSentBy ? ) { throw new Error ( `class method "fromBuffer" is not implemented` ) ; } flag : number ; connectionID : ConnectionID ; sentTime : number ; constructor ( connectionID , flag ) { this . flag = flag ; this . connectionID = connectionID ; this . sentTime = 0 ; } isReset ( ) { return this instanceof ResetPacket ; } isNegotiation ( ) { return this instanceof NegotiationPacket ; } isRegular ( ) { return this instanceof RegularPacket ; } valueOf ( ) { return { connectionID : this . connectionID . valueOf ( ) , flag : this . flag , } ; } toString ( ) { return JSON . stringify ( this . valueOf ( ) ) ; } [ inspect . custom ] ( _depth , _options ) { return `<${ this . constructor . name } ${ this . toString ( ) }>` ; } abstract byteLen ( ) ; abstract writeTo ( bufv ) ; } /** ResetPacket representing a reset Packet. */ export class ResetPacket extends Packet { static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const connectionID = ConnectionID . fromBuffer ( bufv ) ; const quicTag = QuicTags . fromBuffer ( bufv ) ; if ( quicTag . name !== Tag . PRST || ! quicTag . has ( Tag . RNON ) ) { throw new QuicError ( 'QUIC_INVALID_PUBLIC_RST_PACKET' ) ; } return new ResetPacket ( connectionID , quicTag ) ; } nonceProof : Buffer ; packetNumber : PacketNumber | null ; socketAddress : SocketAddress | null ; tags : QuicTags ; constructor ( connectionID , tags ) { super ( connectionID , 0b00001010 ) ; this . tags = tags ; this . packetNumber = null ; this . socketAddress = null ; const nonceProof = tags . get ( Tag . RNON ) ; if ( nonceProof == null ) { throw new QuicError ( 'QUIC_INVALID_PUBLIC_RST_PACKET' ) ; } this . nonceProof = nonceProof ; const rseq = tags . get ( Tag . RSEQ ) ; if ( rseq != null ) { this . packetNumber = PacketNumber . fromBuffer ( new BufferVisitor ( rseq ) , rseq . length ) ; } const cadr = tags . get ( Tag . CADR ) ; if ( cadr != null ) { this . socketAddress = SocketAddress . fromBuffer ( new BufferVisitor ( cadr ) ) ; } } valueOf ( ) { return { connectionID : this . connectionID . valueOf ( ) , flag : this . flag , packetNumber : this . packetNumber == null ? null : this . packetNumber . valueOf ( ) , socketAddress : this . socketAddress == null ? null : this . socketAddress . valueOf ( ) , nonceProof : this . nonceProof , } ; } byteLen ( ) { return 9 + this . tags . byteLen ( ) ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . flag , bufv . start ) ; this . connectionID . writeTo ( bufv ) ; this . tags . writeTo ( bufv ) ; return bufv ; } } /** NegotiationPacket representing a negotiation Packet. */ export class NegotiationPacket extends Packet { static fromConnectionID ( connectionID ) { return new NegotiationPacket ( connectionID , getVersions ( ) ) ; } static fromBuffer ( bufv ) { bufv . walk ( 1 ) ; const connectionID = ConnectionID . fromBuffer ( bufv ) ; const versions = [ ] ; while ( bufv . length > bufv . end ) { bufv . walk ( 4 ) ; const version = bufv . buf . toString ( 'utf8' , bufv . start , bufv . end ) ; if ( ! isSupportedVersion ( version ) ) { throw new QuicError ( 'QUIC_INVALID_VERSION' ) ; } versions . push ( version ) ; } return new NegotiationPacket ( connectionID , versions ) ; } versions : string [ ] ; constructor ( connectionID , versions ) { super ( connectionID , 0b00001001 ) ; this . versions = versions ; } valueOf ( ) { return { connectionID : this . connectionID . valueOf ( ) , flag : this . flag , versions : this . versions , } ; } byteLen ( ) { return 9 + 4 * this . versions . length ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . flag , bufv . start ) ; this . connectionID . writeTo ( bufv ) ; for ( const version of this . versions ) { bufv . walk ( 4 ) ; bufv . buf . write ( version , bufv . start , 4 ) ; } return bufv ; } } /** RegularPacket representing a regular Packet. */ export class RegularPacket extends Packet { static fromBuffer ( bufv , flag , packetSentBy ) { bufv . walk ( 1 ) ; const connectionID = ConnectionID . fromBuffer ( bufv ) ; let version = '' ; const hasVersion = ( flag & 0b1 ) > 0 ; if ( hasVersion ) { bufv . walk ( 4 ) ; version = bufv . buf . toString ( 'utf8' , bufv . start , bufv . end ) ; if ( ! isSupportedVersion ( version ) ) { throw new QuicError ( 'QUIC_INVALID_VERSION' ) ; } } let nonce = null ; if ( packetSentBy === SessionType . SERVER && ( flag & 0b100 ) > 0 ) { bufv . walk ( 32 ) ; nonce = bufv . buf . slice ( bufv . start , bufv . end ) ; if ( nonce . length !== 32 ) { throw new QuicError ( 'QUIC_INTERNAL_ERROR' ) ; } } const packetNumber = PacketNumber . fromBuffer ( bufv , PacketNumber . flagToByteLen ( ( flag & 0b110000 ) >> 4 ) ) ; const packet = new RegularPacket ( connectionID , packetNumber , nonce ) ; if ( version !== '' ) { packet . setVersion ( version ) ; } return packet ; } packetNumber : PacketNumber ; version : string ; nonce : Buffer | null ; frames : Frame [ ] ; isRetransmittable : boolean ; constructor ( connectionID , packetNumber , nonce = null ) { let flag = 0b00001000 ; flag |= ( packetNumber . flagBits ( ) << 4 ) ; if ( nonce != null ) { flag |= 0x04 ; } super ( connectionID , flag ) ; this . packetNumber = packetNumber ; this . version = '' ; this . nonce = nonce ; this . frames = [ ] ; this . isRetransmittable = true ; } valueOf ( ) { return { connectionID : this . connectionID . valueOf ( ) , flag : this . flag , version : this . version , packetNumber : this . packetNumber . valueOf ( ) , nonce : this . nonce , frames : this . frames . map ( ( val ) => val . valueOf ( ) ) , } ; } setVersion ( version ) { this . flag |= 0b00000001 ; this . version = version ; } setPacketNumber ( packetNumber ) { this . packetNumber = packetNumber ; this . flag &= 0b11001111 ; this . flag |= ( packetNumber . flagBits ( ) << 4 ) ; } needAck ( ) { for ( const frame of this . frames ) { if ( frame . isRetransmittable ( ) ) { return true ; } } return false ; } parseFrames ( bufv ) { while ( bufv . end < bufv . length ) { this . addFrames ( parseFrame ( bufv , this . packetNumber ) ) ; } } addFrames ( ... frames ) { this . frames . push ( ... frames ) ; return this ; } headerLen ( ) { let len = 9 ; if ( this . version !== '' ) { len += 4 ; } if ( this . nonce != null ) { len += 32 ; } len += this . packetNumber . byteLen ( ) ; return len ; } byteLen ( ) { let len = this . headerLen ( ) ; for ( const frame of this . frames ) { len += frame . byteLen ( ) ; } return len ; } writeTo ( bufv ) { bufv . walk ( 1 ) ; bufv . buf . writeUInt8 ( this . flag , bufv . start ) ; this . connectionID . writeTo ( bufv ) ; if ( this . version !== '' ) { bufv . walk ( 4 ) ; bufv . buf . write ( this . version , bufv . start , 4 ) ; } if ( this . nonce != null ) { bufv . walk ( 32 ) ; this . nonce . copy ( bufv . buf , bufv . start , 0 , 32 ) ; } this . packetNumber . writeTo ( bufv ) ; for ( const frame of this . frames ) { frame . writeTo ( bufv ) ; } return bufv ; } }