import assert from "assert" ; import deepEquals from "fast-deep-equal" ; import { DNSLabelCoder } from "../DNSLabelCoder" ; import { DecodedData , RType } from "../DNSPacket" ; import { RecordRepresentation , ResourceRecord } from "../ResourceRecord" ; export interface OPTOption { code : number ; data : Buffer ; } export interface OPTFlags { dnsSecOK ? : boolean ; zero ? : number ; } export class OPTRecord extends ResourceRecord { private static readonly EDNS_VERSION = 0 ; private static readonly DNS_SEC_OK_MASK = 0x8000 ; private static readonly NOT_DNS_SEC_OK_MASK = 0x7FFF ; readonly udpPayloadSize : number ; readonly extendedRCode : number ; readonly ednsVersion : number ; readonly flags : OPTFlags ; readonly options : OPTOption [ ] ; constructor ( udpPayloadSize , options ? , extendedRCode ? , flags ? , ednsVersion ? , ttl ? ) ; constructor ( header , options ? , extendedRCode ? , flags ? , ednsVersion ? , ttl ? ) ; constructor ( udpPayloadSize , options ? , extendedRCode ? , flags ? , ednsVersion ? , ttl ? ) { if ( typeof udpPayloadSize === "number" ) { super ( "." , RType . OPT , ttl , false , udpPayloadSize ) ; this . udpPayloadSize = udpPayloadSize ; } else { assert ( udpPayloadSize . type === RType . OPT ) ; super ( udpPayloadSize ) ; this . udpPayloadSize = udpPayloadSize . class ; } this . extendedRCode = extendedRCode || 0 ; this . ednsVersion = ednsVersion || OPTRecord . EDNS_VERSION ; this . flags = { dnsSecOK : flags ?. dnsSecOK || false , zero : flags ?. zero || 0 , ... flags , } ; this . options = options || [ ] ; } protected getRDataEncodingLength ( ) { let length = 0 ; for ( const option of this . options ) { length += 2 + 2 + option . data . length ; } return length ; } protected encodeRData ( coder , buffer , offset ) { const oldOffset = offset ; const classOffset = offset - 8 ; const ttlOffset = offset - 6 ; buffer . writeUInt16BE ( this . udpPayloadSize , classOffset ) ; buffer . writeUInt8 ( this . extendedRCode , ttlOffset ) ; buffer . writeUInt8 ( this . ednsVersion , ttlOffset + 1 ) ; let flags = this . flags . zero || 0 ; if ( this . flags . dnsSecOK ) { flags |= OPTRecord . DNS_SEC_OK_MASK ; } buffer . writeUInt16BE ( flags , ttlOffset + 2 ) ; for ( const option of this . options ) { buffer . writeUInt16BE ( option . code , offset ) ; offset += 2 ; buffer . writeUInt16BE ( option . data . length , offset ) ; offset += 2 ; option . data . copy ( buffer , offset ) ; offset += option . data . length ; } return offset - oldOffset ; } public static decodeData ( coder , header , buffer , offset ) { const oldOffset = offset ; const classOffset = offset - 8 ; const ttlOffset = offset - 6 ; const udpPayloadSize = buffer . readUInt16BE ( classOffset ) ; const extendedRCode = buffer . readUInt8 ( ttlOffset ) ; const ednsVersion = buffer . readUInt8 ( ttlOffset + 1 ) ; const flagsField = buffer . readUInt16BE ( ttlOffset + 2 ) ; const flags = { dnsSecOK : ! ! ( flagsField & OPTRecord . DNS_SEC_OK_MASK ) , zero : flagsField & OPTRecord . NOT_DNS_SEC_OK_MASK , } ; const options = [ ] ; while ( offset < buffer . length ) { const code = buffer . readUInt16BE ( offset ) ; offset += 2 ; const length = buffer . readUInt16BE ( offset ) ; offset += 2 ; const data = buffer . slice ( offset , offset + length ) ; offset += length ; options . push ( { code : code , data : data , } ) ; } header . class = udpPayloadSize ; header . ttl = 4500 ; return { data : new OPTRecord ( header , options , extendedRCode , flags , ednsVersion ) , readBytes : offset - oldOffset , } ; } public clone ( ) { return new OPTRecord ( this . getRecordRepresentation ( ) , this . options , this . extendedRCode , this . flags , this . ednsVersion ) ; } public dataAsString ( ) { return `${ this . udpPayloadSize } ${ this . extendedRCode } ${ this . ednsVersion } ${ JSON . stringify ( this . flags ) } [${ this . options . map ( opt => `${ opt . code } ${ opt . data . toString ( "base64" ) }` ) . join ( "," ) }]` ; } public dataEquals ( record ) { return this . udpPayloadSize === record . udpPayloadSize && this . extendedRCode === record . extendedRCode && this . ednsVersion === record . ednsVersion && OPTRecord . optionsEquality ( this . options , record . options ) && deepEquals ( this . flags , record . flags ) ; } private static optionsEquality ( a , b ) { if ( a . length !== b . length ) { return false ; } for ( let i = 0 ; i < a . length ; i ++ ) { if ( a [ i ] . code !== b [ i ] . code ) { return false ; } else if ( a [ i ] . data . toString ( "hex" ) !== b [ i ] . data . toString ( "hex" ) ) { return false ; } } return true ; } }