'use strict' ; import { inspect } from 'util' ; import { randomBytes } from 'crypto' ; import { AddressInfo } from 'net' ; import { bytesFromIP , bytesToIP } from '@fidm/x509' ; import { Visitor , BufferVisitor , readUnsafeUInt , writeUnsafeUInt } from './common' ; import { QuicError } from './error' ; import { kVal } from './symbol' ; const QUIC_VERSIONS = [ 'Q039' ] ; export enum SessionType { SERVER = 0 , CLIENT = 1 } export enum FamilyType { IPv4 = 'IPv4' , IPv6 = 'IPv6' } /**
 * Returns supported version.
 */ export function getVersion ( ) { return QUIC_VERSIONS [ 0 ] ; } /**
 * Returns supported versions array.
 */ export function getVersions ( ) { return QUIC_VERSIONS ; } /**
 * Returns true if the server supports this version.
 */ export function isSupportedVersion ( version ) { return QUIC_VERSIONS . includes ( version ) ; } /**
 * Chooses the best version in the overlap of ours and theirs.
 */ export function chooseVersion ( theirs ) { for ( const v of theirs ) { if ( isSupportedVersion ( v ) ) { return v ; } } return '' ; } /** Protocol representing a base protocol. */ export abstract class Protocol { static fromBuffer ( _bufv , _len ? ) { throw new Error ( `class method "fromBuffer" is not implemented` ) ; } protected readonly [ kVal ] : any ; constructor ( val ) { this [ kVal ] = val ; } abstract equals ( other ) ; abstract byteLen ( arg ? ) ; abstract writeTo ( bufv , arg ? ) ; abstract valueOf ( ) ; abstract toString ( ) ; [ inspect . custom ] ( _depth , _options ) { return `<${ this . constructor . name } ${ this . toString ( ) }>` ; } } const ConnectionIDReg = /^[0-9a-f]{16}$/ ; /** ConnectionID representing a connectionID. */ export class ConnectionID extends Protocol { static fromBuffer ( bufv ) { bufv . mustWalk ( 8 , 'QUIC_INTERNAL_ERROR' ) ; return new ConnectionID ( bufv . buf . toString ( 'hex' , bufv . start , bufv . end ) ) ; } static random ( ) { return new ConnectionID ( randomBytes ( 8 ) . toString ( 'hex' ) ) ; } constructor ( id ) { if ( ! ConnectionIDReg . test ( id ) ) { throw new Error ( 'invalid Connection ID' ) ; } super ( id ) ; } return { string } valueOf ( ) { return this [ kVal ] ; } equals ( other ) { return ( other instanceof ConnectionID ) && this . valueOf ( ) === other . valueOf ( ) ; } byteLen ( ) { return 8 ; } writeTo ( bufv ) { bufv . walk ( 8 ) ; bufv . buf . write ( this [ kVal ] , bufv . start , 8 , 'hex' ) ; return bufv ; } toString ( ) { return this [ kVal ] ; } } /** PacketNumber representing a packetNumber. */ export class PacketNumber extends Protocol { static flagToByteLen ( flagBits ) { if ( ( flagBits & 0b11 ) !== flagBits ) { throw new Error ( 'invalid flagBits' ) ; } return flagBits > 0 ? ( flagBits * 2 ) : 1 ; } static fromBuffer ( bufv , len ) { bufv . mustWalk ( len , 'QUIC_INTERNAL_ERROR' ) ; return new PacketNumber ( bufv . buf . readUIntBE ( bufv . start , len ) ) ; } constructor ( val ) { if ( ! Number . isInteger ( val ) || val < 1 || val > 0xffffffffffff ) { throw new Error ( `invalid PacketNumber val ${ val }` ) ; } super ( val ) ; } valueOf ( ) { return this [ kVal ] ; } nextNumber ( ) { return new PacketNumber ( this [ kVal ] + 1 ) ; } prevNumber ( ) { return new PacketNumber ( this [ kVal ] - 1 ) ; } isLimitReached ( ) { return this [ kVal ] >= 0xffffffffffff ; } delta ( other ) { return Math . abs ( this . valueOf ( ) - other . valueOf ( ) ) ; } closestTo ( a , b ) { return this . delta ( a ) < this . delta ( b ) ? a : b ; } flagBits ( ) { const byteLen = this . byteLen ( ) ; if ( byteLen === 1 ) { return 0 ; } return byteLen / 2 ; } equals ( other ) { return ( other instanceof PacketNumber ) && this . valueOf ( ) === other . valueOf ( ) ; } byteLen ( isFull = false ) { if ( ! isFull ) { const value = this [ kVal ] ; if ( value <= 0xff ) { return 1 ; } else if ( value <= 0xffff ) { return 2 ; } else if ( value <= 0xffffffff ) { return 4 ; } } return 6 ; } writeTo ( bufv , isFull = false ) { const len = isFull ? 6 : this . byteLen ( ) ; bufv . walk ( len ) ; bufv . buf . writeUIntBE ( this [ kVal ] , bufv . start , len ) ; return bufv ; } toString ( ) { return String ( this [ kVal ] ) ; } } /** StreamID representing a streamID. */ export class StreamID extends Protocol { /**
     * 2 bits -> 8/8, 16/8, 24/8, 32/8
     */ static flagToByteLen ( flagBits ) { if ( ( flagBits & 0b11 ) !== flagBits ) { throw new Error ( 'invalid flagBits' ) ; } return flagBits + 1 ; } static fromBuffer ( bufv , len ) { bufv . mustWalk ( len , 'QUIC_INVALID_STREAM_DATA' ) ; return new StreamID ( bufv . buf . readUIntBE ( bufv . start , len ) ) ; } constructor ( id ) { if ( ! Number . isInteger ( id ) || id < 0 || id > 0xffffffff ) { throw new Error ( `invalid Stream ID ${ id }` ) ; } super ( id ) ; } valueOf ( ) { return this [ kVal ] ; } flagBits ( ) { return this . byteLen ( ) - 1 ; } nextID ( ) { const value = this [ kVal ] + 2 ; return new StreamID ( value <= 0xffffffff ? value : ( value - 0xffffffff ) ) ; } prevID ( ) { return new StreamID ( this [ kVal ] - 2 ) ; } equals ( other ) { return ( other instanceof StreamID ) && this . valueOf ( ) === other . valueOf ( ) ; } byteLen ( isFull = false ) { if ( ! isFull ) { const value = this [ kVal ] ; if ( value <= 0xff ) { return 1 ; } else if ( value <= 0xffff ) { return 2 ; } else if ( value <= 0xffffff ) { return 3 ; } } return 4 ; } writeTo ( bufv , isFull = false ) { const len = isFull ? 4 : this . byteLen ( ) ; bufv . walk ( len ) ; bufv . buf . writeUIntBE ( this [ kVal ] , bufv . start , len ) ; return bufv ; } toString ( ) { return String ( this [ kVal ] ) ; } } /** Offset representing a data offset. */ export class Offset extends Protocol { /**
     * 3 bits -> 0, 16/8, 24/8, 32/8, 40/8, 48/8, 56/8, 64/8
     */ static flagToByteLen ( flagBits ) { if ( ( flagBits & 0b111 ) !== flagBits ) { throw new Error ( 'invalid flagBits' ) ; } return flagBits > 0 ? ( flagBits + 1 ) : 0 ; } static fromBuffer ( bufv , len ) { bufv . mustWalk ( len , 'QUIC_INTERNAL_ERROR' ) ; return new Offset ( readUnsafeUInt ( bufv . buf , bufv . start , len ) ) ; } constructor ( offset ) { if ( ! Number . isSafeInteger ( offset ) || offset < 0 ) { throw new Error ( `invalid Offset ${ offset }` ) ; } super ( offset ) ; } valueOf ( ) { return this [ kVal ] ; } equals ( other ) { return this . valueOf ( ) === other . valueOf ( ) ; } gt ( other ) { return this . valueOf ( ) > other . valueOf ( ) ; } byteLen ( isFull = false ) { if ( ! isFull ) { const value = this [ kVal ] ; if ( value === 0 ) { return 0 ; } else if ( value <= 0xffff ) { return 2 ; } else if ( value <= 0xffffff ) { return 3 ; } else if ( value <= 0xffffffff ) { return 4 ; } else if ( value <= 0xffffffffff ) { return 5 ; } else if ( value <= 0xffffffffffff ) { return 6 ; } return 7 ; } return 8 ; } /**
     * 0, 16/8, 24/8, 32/8, 40/8, 48/8, 56/8, 64/8 -> 3 bits
     */ flagBits ( ) { const byteLen = this . byteLen ( ) ; if ( byteLen === 0 ) { return 0 ; } return byteLen > 1 ? ( byteLen - 1 ) : 1 ; } writeTo ( bufv , isFull = false ) { const len = isFull ? 8 : this . byteLen ( ) ; bufv . mustWalk ( len , 'QUIC_INTERNAL_ERROR' ) ; writeUnsafeUInt ( bufv . buf , this [ kVal ] , bufv . start , len ) ; return bufv ; } toString ( ) { return String ( this [ kVal ] ) ; } } /** SocketAddress representing a socket address. */ export class SocketAddress extends Protocol { static fromBuffer ( bufv ) { const obj = { address : '' , family : FamilyType . IPv4 , port : 0 , } ; bufv . mustWalk ( 2 , 'QUIC_INTERNAL_ERROR' ) ; const family = bufv . buf . readUInt16BE ( bufv . start ) ; if ( family === 0x02 ) { obj . family = FamilyType . IPv4 ; bufv . mustWalk ( 4 , 'QUIC_INTERNAL_ERROR' ) ; obj . address = bytesToIP ( bufv . buf . slice ( bufv . start , bufv . end ) ) ; bufv . mustWalk ( 2 , 'QUIC_INTERNAL_ERROR' ) ; obj . port = bufv . buf . readUInt16BE ( bufv . start ) ; } else if ( family === 0x0a ) { obj . family = FamilyType . IPv6 ; bufv . mustWalk ( 16 , 'QUIC_INTERNAL_ERROR' ) ; obj . address = bytesToIP ( bufv . buf . slice ( bufv . start , bufv . end ) ) ; bufv . mustWalk ( 2 , 'QUIC_INTERNAL_ERROR' ) ; obj . port = bufv . buf . readUInt16BE ( bufv . start ) ; } else { throw new Error ( 'invalid SocketAddress buffer' ) ; } return new SocketAddress ( obj ) ; } port : number ; address : string ; family : FamilyType ; constructor ( obj ) { if ( ! isAddress ( obj ) ) { throw new Error ( `invalid Socket Address ${ JSON . stringify ( obj ) }` ) ; } super ( obj . address ) ; this . port = obj . port ; this . family = obj . family as FamilyType ; this . address = obj . address ; } valueOf ( ) { return { address : this . address , family : this . family as string , port : this . port , } ; } equals ( other ) { if ( ! ( other instanceof SocketAddress ) ) { return false ; } return this . family === other . family && this . port === other . port && this . address === other . address ; } byteLen ( ) { return this . family === FamilyType . IPv4 ? 8 : 20 ; } writeTo ( bufv ) { if ( this . family === FamilyType . IPv4 ) { bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( 0x02 , bufv . start ) ; const buf = bytesFromIP ( this . address ) ; if ( buf == null || buf . length !== 4 ) { throw new Error ( `Invalid IPv4 address ${ this . address }` ) ; } bufv . walk ( 4 ) ; buf . copy ( bufv . buf , bufv . start , 0 , 4 ) ; bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( this . port , bufv . start ) ; } else { bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( 0x0a , bufv . start ) ; const buf = bytesFromIP ( this . address ) ; if ( buf == null || buf . length !== 16 ) { throw new Error ( `Invalid IPv6 address ${ this . address }` ) ; } bufv . walk ( 16 ) ; buf . copy ( bufv . buf , bufv . start , 0 , 16 ) ; bufv . walk ( 2 ) ; bufv . buf . writeUInt16BE ( this . port , bufv . start ) ; } return bufv ; } toString ( ) { return JSON . stringify ( this . valueOf ( ) ) ; } } /** QuicTags representing a QUIC tag. */ export class QuicTags extends Protocol { static fromBuffer ( bufv ) { bufv . mustWalk ( 4 , 'QUIC_INTERNAL_ERROR' ) ; const tagName = bufv . buf . readUInt32BE ( bufv . start ) ; const quicTag = new QuicTags ( tagName ) ; bufv . mustWalk ( 4 , 'QUIC_INTERNAL_ERROR' ) ; let count = bufv . buf . readInt16LE ( bufv . start ) ; const baseOffset = bufv . end + 8 * count ; const v2 = new Visitor ( baseOffset ) ; while ( count -- > 0 ) { bufv . mustWalk ( 4 , 'QUIC_INTERNAL_ERROR' ) ; const key = bufv . buf . readInt32BE ( bufv . start ) ; bufv . mustWalk ( 4 , 'QUIC_INTERNAL_ERROR' ) ; v2 . walk ( 0 ) ; v2 . end = baseOffset + bufv . buf . readInt32LE ( bufv . start ) ; if ( bufv . length < v2 . end ) { throw new QuicError ( 'QUIC_INTERNAL_ERROR' ) ; } const val = bufv . buf . slice ( v2 . start , v2 . end ) ; quicTag . set ( key , val ) ; } bufv . reset ( v2 . end , v2 . end ) ; return quicTag ; } name : Tag ; tags : Map < Tag , Buffer > ; constructor ( name ) { super ( name ) ; this . name = name ; this . tags = new Map ( ) ; } valueOf ( ) { const tags = { } ; for ( const [ key , value ] of this . tags ) { tags [ Tag [ key ] ] = value ; } return { name : Tag [ this . name ] , tags , } ; } get size ( ) : number { return this . tags . size ; } [ Symbol . iterator ] ( ) { return this . tags [ Symbol . iterator ] ( ) ; } set ( key , val ) { this . tags . set ( key , val ) ; } get ( key ) { const buf = this . tags . get ( key ) ; return buf == null ? null : buf ; } has ( key ) { return this . tags . has ( key ) ; } equals ( other ) { if ( ! ( other instanceof QuicTags ) ) { return false ; } if ( this . name !== other . name || this . tags . size !== other . tags . size ) { return false ; } for ( const key of this . tags . keys ( ) ) { const a = this . tags . get ( key ) ; const b = other . tags . get ( key ) ; if ( a == null || b == null || ! a . equals ( b ) ) { return false ; } } return true ; } byteLen ( ) { let byteLen = 8 ; for ( const buf of this . tags . values ( ) ) { byteLen += 8 + buf . length ; } return byteLen ; } writeTo ( bufv ) { bufv . walk ( 4 ) ; bufv . buf . writeUInt32BE ( this . name , bufv . start ) ; bufv . walk ( 4 ) ; const size = this . tags . size ; bufv . buf . writeUInt16LE ( size , bufv . start ) ; bufv . buf . writeUInt16LE ( 0 , bufv . start + 2 ) ; let baseOffset = 0 ; const v = new Visitor ( bufv . end + 8 * size ) ; const keys = Array . from ( this . tags . keys ( ) ) ; keys . sort ( ( a , b ) => a - b ) ; for ( const key of keys ) { const val = this . tags . get ( key ) ; if ( val == null ) { throw new QuicError ( 'QUIC_INTERNAL_ERROR' ) ; } bufv . walk ( 4 ) ; bufv . buf . writeUInt32BE ( key , bufv . start ) ; bufv . walk ( 4 ) ; baseOffset += val . length ; bufv . buf . writeUInt32LE ( baseOffset , bufv . start ) ; v . walk ( val . length ) ; val . copy ( bufv . buf , v . start , 0 , val . length ) ; } bufv . reset ( v . end , v . end ) ; return bufv ; } toString ( ) { return JSON . stringify ( this . valueOf ( ) ) ; } } export enum Tag { CHLO = toTag ( 'C' , 'H' , 'L' , 'O' ) , SHLO = toTag ( 'S' , 'H' , 'L' , 'O' ) , SCFG = toTag ( 'S' , 'C' , 'F' , 'G' ) , REJ = toTag ( 'R' , 'E' , 'J' , '\u{0}' ) , SREJ = toTag ( 'S' , 'R' , 'E' , 'J' ) , CETV = toTag ( 'C' , 'E' , 'T' , 'V' ) , PRST = toTag ( 'P' , 'R' , 'S' , 'T' ) , SCUP = toTag ( 'S' , 'C' , 'U' , 'P' ) , ALPN = toTag ( 'A' , 'L' , 'P' , 'N' ) , P256 = toTag ( 'P' , '2' , '5' , '6' ) , C255 = toTag ( 'C' , '2' , '5' , '5' ) , AESG = toTag ( 'A' , 'E' , 'S' , 'G' ) , CC20 = toTag ( 'C' , 'C' , '2' , '0' ) , SRBF = toTag ( 'S' , 'R' , 'B' , 'F' ) , QBIC = toTag ( 'Q' , 'B' , 'I' , 'C' ) , AFCW = toTag ( 'A' , 'F' , 'C' , 'W' ) , IFW5 = toTag ( 'I' , 'F' , 'W' , '5' ) , IFW6 = toTag ( 'I' , 'F' , 'W' , '6' ) , IFW7 = toTag ( 'I' , 'F' , 'W' , '7' ) , IFW8 = toTag ( 'I' , 'F' , 'W' , '8' ) , IFW9 = toTag ( 'I' , 'F' , 'W' , '9' ) , IFWA = toTag ( 'I' , 'F' , 'W' , 'a' ) , TBBR = toTag ( 'T' , 'B' , 'B' , 'R' ) , '1RTT' = toTag ( '1' , 'R' , 'T' , 'T' ) , '2RTT' = toTag ( '2' , 'R' , 'T' , 'T' ) , LRTT = toTag ( 'L' , 'R' , 'T' , 'T' ) , BBRR = toTag ( 'B' , 'B' , 'R' , 'R' ) , BBR1 = toTag ( 'B' , 'B' , 'R' , '1' ) , BBR2 = toTag ( 'B' , 'B' , 'R' , '2' ) , RENO = toTag ( 'R' , 'E' , 'N' , 'O' ) , TPCC = toTag ( 'P' , 'C' , 'C' , '\u{0}' ) , BYTE = toTag ( 'B' , 'Y' , 'T' , 'E' ) , IW03 = toTag ( 'I' , 'W' , '0' , '3' ) , IW10 = toTag ( 'I' , 'W' , '1' , '0' ) , IW20 = toTag ( 'I' , 'W' , '2' , '0' ) , IW50 = toTag ( 'I' , 'W' , '5' , '0' ) , '1CON' = toTag ( '1' , 'C' , 'O' , 'N' ) , NTLP = toTag ( 'N' , 'T' , 'L' , 'P' ) , NCON = toTag ( 'N' , 'C' , 'O' , 'N' ) , NRTO = toTag ( 'N' , 'R' , 'T' , 'O' ) , UNDO = toTag ( 'U' , 'N' , 'D' , 'O' ) , TIME = toTag ( 'T' , 'I' , 'M' , 'E' ) , ATIM = toTag ( 'A' , 'T' , 'I' , 'M' ) , MIN1 = toTag ( 'M' , 'I' , 'N' , '1' ) , MIN4 = toTag ( 'M' , 'I' , 'N' , '4' ) , TLPR = toTag ( 'T' , 'L' , 'P' , 'R' ) , ACKD = toTag ( 'A' , 'C' , 'K' , 'D' ) , AKD2 = toTag ( 'A' , 'K' , 'D' , '2' ) , AKD3 = toTag ( 'A' , 'K' , 'D' , '3' ) , AKD4 = toTag ( 'A' , 'K' , 'D' , '4' ) , AKDU = toTag ( 'A' , 'K' , 'D' , 'U' ) , SSLR = toTag ( 'S' , 'S' , 'L' , 'R' ) , NPRR = toTag ( 'N' , 'P' , 'R' , 'R' ) , '5RTO' = toTag ( '5' , 'R' , 'T' , 'O' ) , '3RTO' = toTag ( '3' , 'R' , 'T' , 'O' ) , CTIM = toTag ( 'C' , 'T' , 'I' , 'M' ) , DHDT = toTag ( 'D' , 'H' , 'D' , 'T' ) , CONH = toTag ( 'C' , 'O' , 'N' , 'H' ) , LFAK = toTag ( 'L' , 'F' , 'A' , 'K' ) , SMHL = toTag ( 'S' , 'M' , 'H' , 'L' ) , CCVX = toTag ( 'C' , 'C' , 'V' , 'X' ) , CBQT = toTag ( 'C' , 'B' , 'Q' , 'T' ) , BLMX = toTag ( 'B' , 'L' , 'M' , 'X' ) , CPAU = toTag ( 'C' , 'P' , 'A' , 'U' ) , NSTP = toTag ( 'N' , 'S' , 'T' , 'P' ) , TCID = toTag ( 'T' , 'C' , 'I' , 'D' ) , MPTH = toTag ( 'M' , 'P' , 'T' , 'H' ) , NCMR = toTag ( 'N' , 'C' , 'M' , 'R' ) , BWRE = toTag ( 'B' , 'W' , 'R' , 'E' ) , BWMX = toTag ( 'B' , 'W' , 'M' , 'X' ) , BWRS = toTag ( 'B' , 'W' , 'R' , 'S' ) , BWS2 = toTag ( 'B' , 'W' , 'S' , '2' ) , MTUH = toTag ( 'M' , 'T' , 'U' , 'H' ) , MTUL = toTag ( 'M' , 'T' , 'U' , 'L' ) , ASYN = toTag ( 'A' , 'S' , 'Y' , 'N' ) , SYNC = toTag ( 'S' , 'Y' , 'N' , 'C' ) , FHL2 = toTag ( 'F' , 'H' , 'L' , '2' ) , X509 = toTag ( 'X' , '5' , '0' , '9' ) , X59R = toTag ( 'X' , '5' , '9' , 'R' ) , CHID = toTag ( 'C' , 'H' , 'I' , 'D' ) , VER = toTag ( 'V' , 'E' , 'R' , '\u{0}' ) , NONC = toTag ( 'N' , 'O' , 'N' , 'C' ) , NONP = toTag ( 'N' , 'O' , 'N' , 'P' ) , KEXS = toTag ( 'K' , 'E' , 'X' , 'S' ) , AEAD = toTag ( 'A' , 'E' , 'A' , 'D' ) , COPT = toTag ( 'C' , 'O' , 'P' , 'T' ) , CLOP = toTag ( 'C' , 'L' , 'O' , 'P' ) , ICSL = toTag ( 'I' , 'C' , 'S' , 'L' ) , SCLS = toTag ( 'S' , 'C' , 'L' , 'S' ) , MSPC = toTag ( 'M' , 'S' , 'P' , 'C' ) , MIDS = toTag ( 'M' , 'I' , 'D' , 'S' ) , IRTT = toTag ( 'I' , 'R' , 'T' , 'T' ) , SWND = toTag ( 'S' , 'W' , 'N' , 'D' ) , SNI = toTag ( 'S' , 'N' , 'I' , '\u{0}' ) , PUBS = toTag ( 'P' , 'U' , 'B' , 'S' ) , SCID = toTag ( 'S' , 'C' , 'I' , 'D' ) , ORBT = toTag ( 'O' , 'B' , 'I' , 'T' ) , PDMD = toTag ( 'P' , 'D' , 'M' , 'D' ) , PROF = toTag ( 'P' , 'R' , 'O' , 'F' ) , CCS = toTag ( 'C' , 'C' , 'S' , '\u{0}' ) , CCRT = toTag ( 'C' , 'C' , 'R' , 'T' ) , EXPY = toTag ( 'E' , 'X' , 'P' , 'Y' ) , STTL = toTag ( 'S' , 'T' , 'T' , 'L' ) , SFCW = toTag ( 'S' , 'F' , 'C' , 'W' ) , CFCW = toTag ( 'C' , 'F' , 'C' , 'W' ) , UAID = toTag ( 'U' , 'A' , 'I' , 'D' ) , XLCT = toTag ( 'X' , 'L' , 'C' , 'T' ) , TBKP = toTag ( 'T' , 'B' , 'K' , 'P' ) , TB10 = toTag ( 'T' , 'B' , '1' , '0' ) , RREJ = toTag ( 'R' , 'R' , 'E' , 'J' ) , RCID = toTag ( 'R' , 'C' , 'I' , 'D' ) , CADR = toTag ( 'C' , 'A' , 'D' , 'R' ) , ASAD = toTag ( 'A' , 'S' , 'A' , 'D' ) , CIDK = toTag ( 'C' , 'I' , 'D' , 'K' ) , CIDS = toTag ( 'C' , 'I' , 'D' , 'S' ) , RNON = toTag ( 'R' , 'N' , 'O' , 'N' ) , RSEQ = toTag ( 'R' , 'S' , 'E' , 'Q' ) , PAD = toTag ( 'P' , 'A' , 'D' , '\u{0}' ) , SPSH = toTag ( 'S' , 'P' , 'S' , 'H' ) , SNO = toTag ( 'S' , 'N' , 'O' , '\u{0}' ) , STK = toTag ( 'S' , 'T' , 'K' , '\u{0}' ) , CRT = toTag ( 'C' , 'R' , 'T' , '\u{ff}' ) , CSCT = toTag ( 'C' , 'S' , 'C' , 'T' ) } function toTag ( a , b , c , d ) { return a . charCodeAt ( 0 ) * ( 0xffffff + 1 ) + b . charCodeAt ( 0 ) * ( 0xffff + 1 ) + c . charCodeAt ( 0 ) * ( 0xff + 1 ) + d . charCodeAt ( 0 ) ; } function isAddress ( address ) { return address != null && address . port >= 0 && Number . isInteger ( address . port ) && typeof address . address === 'string' && ( address . family === FamilyType . IPv4 || address . family === FamilyType . IPv6 ) ; }