import assert from "assert" ; import createDebug from "debug" ; import { AddressInfo } from "net" ; import { dnsLowerCase } from "../util/dns-equal" ; import { dnsTypeToString } from "./dns-string-utils" ; import { DNSLabelCoder , NonCompressionLabelCoder } from "./DNSLabelCoder" ; import { DecodedData , DNSRecord , OptionalDecodedData , RClass , RType } from "./DNSPacket" ; const debug = createDebug ( "ciao:decoder" ) ; export interface RecordRepresentation { name : string ; type : RType ; class : RClass ; ttl : number ; flushFlag : boolean ; } interface RecordHeaderData extends RecordRepresentation { rDataLength : number ; } export type RRDecoder = ( coder : DNSLabelCoder , header : RecordRepresentation , buffer : Buffer , offset : number ) => DecodedData < ResourceRecord > ; export abstract class ResourceRecord implements DNSRecord { public static readonly typeToRecordDecoder : Map < RType , RRDecoder > = new Map ( ) ; private static readonly FLUSH_MASK = 0x8000 ; private static readonly NOT_FLUSH_MASK = 0x7FFF ; public static readonly RR_DEFAULT_TTL_SHORT = 120 ; public static readonly RR_DEFAULT_TTL = 4500 ; readonly name : string ; private lowerCasedName ? : string ; readonly type : RType ; readonly class : RClass ; ttl : number ; flushFlag = false ; protected constructor ( headerData ) ; protected constructor ( name , type , ttl ? , flushFlag ? , clazz ? ) ; protected constructor ( name , type ? , ttl = ResourceRecord . RR_DEFAULT_TTL , flushFlag = false , clazz = RClass . IN ) { if ( typeof name === "string" ) { if ( ! name . endsWith ( "." ) ) { name = name + "." ; } this . name = name ; this . type = type ! ; this . class = clazz ; this . ttl = ttl ; this . flushFlag = flushFlag ; } else { this . name = name . name ; this . type = name . type ; this . class = name . class ; this . ttl = name . ttl ; this . flushFlag = name . flushFlag ; } } public getLowerCasedName ( ) { return this . lowerCasedName || ( this . lowerCasedName = dnsLowerCase ( this . name ) ) ; } public getEncodingLength ( coder ) { return coder . getNameLength ( this . name ) + 10 + this . getRDataEncodingLength ( coder ) ; } public encode ( coder , buffer , offset ) { const oldOffset = offset ; const nameLength = coder . encodeName ( this . name , offset ) ; offset += nameLength ; buffer . writeUInt16BE ( this . type , offset ) ; offset += 2 ; let rClass = this . class ; if ( this . flushFlag ) { rClass |= ResourceRecord . FLUSH_MASK ; } buffer . writeUInt16BE ( rClass , offset ) ; offset += 2 ; buffer . writeUInt32BE ( this . ttl , offset ) ; offset += 4 ; const dataLength = this . encodeRData ( coder , buffer , offset + 2 ) ; buffer . writeUInt16BE ( dataLength , offset ) ; offset += 2 + dataLength ; return offset - oldOffset ; } public getRawData ( ) { const coder = NonCompressionLabelCoder . INSTANCE ; const length = this . getRDataEncodingLength ( coder ) ; const buffer = Buffer . allocUnsafe ( length ) ; coder . initBuf ( buffer ) ; const writtenBytes = this . encodeRData ( coder , buffer , 0 ) ; assert ( writtenBytes === buffer . length , "Didn't completely write to the buffer! (" + writtenBytes + "!=" + buffer . length + ")" ) ; coder . initBuf ( ) ; return buffer ; } protected abstract getRDataEncodingLength ( coder ) ; protected abstract encodeRData ( coder , buffer , offset ) ; public abstract dataAsString ( ) ; public abstract clone ( ) ; param record public abstract dataEquals ( record ) ; public static clone < T extends ResourceRecord > ( records ) { return records . map ( record => record . clone ( ) ) ; } protected getRecordRepresentation ( ) { return { name : this . name , type : this . type , class : this . class , ttl : this . ttl , flushFlag : this . flushFlag , } ; } param record public aboutEqual ( record ) { return this . type === record . type && this . name === record . name && this . class === record . class && this . dataEquals ( record ) ; } public representsSameData ( record ) { return this . type === record . type && this . name === record . name && this . class === record . class ; } public asString ( ) { return `RR ${ this . name } ${ this . type } ${ this . class } ${ this . dataAsString ( ) }` ; } public static decode ( context , coder , buffer , offset ) { const oldOffset = offset ; const decodedHeader = this . decodeRecordHeader ( coder , buffer , offset ) ; offset += decodedHeader . readBytes ; const header = decodedHeader . data ; const rrDecoder = this . typeToRecordDecoder . get ( header . type ) ; if ( ! rrDecoder ) { return { readBytes : ( offset + header . rDataLength ) - oldOffset } ; } coder . initRRLocation ( oldOffset , offset , header . rDataLength ) ; const rdata = buffer . slice ( 0 , offset + header . rDataLength ) ; let decodedRecord ; try { decodedRecord = rrDecoder ( coder , header , rdata , offset ) ; } catch ( error ) { debug ( `Received malformed rdata section for ${ dnsTypeToString ( header . type ) } ${ header . name } ${ header . ttl } \
from ${ context . address }:${ context . port } with data '${ rdata . slice ( offset ) . toString ( "hex" ) }': ${ error . stack }` ) ; return { readBytes : ( offset + header . rDataLength ) - oldOffset } ; } offset += decodedRecord . readBytes ; coder . clearRRLocation ( ) ; return { data : decodedRecord . data , readBytes : offset - oldOffset , } ; } protected static decodeRecordHeader ( coder , buffer , offset ) { const oldOffset = offset ; const decodedName = coder . decodeName ( offset ) ; offset += decodedName . readBytes ; const type = buffer . readUInt16BE ( offset ) as RType ; offset += 2 ; const rClass = buffer . readUInt16BE ( offset ) ; offset += 2 ; let clazz ; let flushFlag = false ; if ( type !== RType . OPT ) { clazz = ( rClass & this . NOT_FLUSH_MASK ) as RClass ; flushFlag = ! ! ( rClass & this . FLUSH_MASK ) ; } else { clazz = rClass ; } const ttl = buffer . readUInt32BE ( offset ) ; offset += 4 ; const rDataLength = buffer . readUInt16BE ( offset ) ; offset += 2 ; const rHeader = { name : decodedName . data , type : type , class : clazz , ttl : ttl , flushFlag : flushFlag , rDataLength : rDataLength , } ; return { data : rHeader , readBytes : offset - oldOffset , } ; } }