@@ -81,6 +81,28 @@ test('Package#toIPv6', function() {
8181 assert . equal ( Packet . toIPv6 ( [ 9734 , 18176 , 12552 , 0 , 0 , 0 , 44098 , 10984 ] ) , '2606:4700:3108::ac42:2ae8' ) ;
8282} ) ;
8383
84+ test ( 'Package#toIPv6 RFC 5952 — leading-zero addresses' , function ( ) {
85+ assert . equal ( Packet . toIPv6 ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ] ) , '::1' ) ;
86+ assert . equal ( Packet . toIPv6 ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) , '::' ) ;
87+ assert . equal ( Packet . toIPv6 ( [ 0 , 0 , 0 , 0 , 0 , 0xffff , 0xc000 , 0x0201 ] ) , '::ffff:c000:201' ) ;
88+ } ) ;
89+
90+ test ( 'Package#toIPv6 RFC 5952 — trailing-zero addresses' , function ( ) {
91+ assert . equal ( Packet . toIPv6 ( [ 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) , '1::' ) ;
92+ assert . equal ( Packet . toIPv6 ( [ 0x2001 , 0xdb8 , 0 , 0 , 0 , 0 , 0 , 0 ] ) , '2001:db8::' ) ;
93+ } ) ;
94+
95+ test ( 'Package#toIPv6 RFC 5952 — single zero group is not compressed' , function ( ) {
96+ // §4.2.2: "::" MUST NOT be used to shorten just one 16-bit 0 field.
97+ assert . equal ( Packet . toIPv6 ( [ 1 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ] ) , '1:0:1:1:1:1:1:1' ) ;
98+ } ) ;
99+
100+ test ( 'Package#toIPv6 RFC 5952 — first run wins on tie' , function ( ) {
101+ // §4.2.3: when there is more than one run of equal maximum length,
102+ // the first is shortened.
103+ assert . equal ( Packet . toIPv6 ( [ 1 , 0 , 0 , 1 , 0 , 0 , 1 , 1 ] ) , '1::1:0:0:1:1' ) ;
104+ } ) ;
105+
84106test ( 'Package#fromIPv6' , function ( ) {
85107 assert . deepEqual ( Packet . fromIPv6 ( '2a04:4e42:200::323' ) , [
86108 '2a04' , '4e42' , '0200' , '0' , '0' , '0' , '0' , '0323' ] ) ;
@@ -221,11 +243,13 @@ test('EDNS.ECS#encode', function() {
221243 new Packet . Resource . EDNS . ECS ( '10.11.12.13/24' ) ,
222244 ] ) ;
223245
246+ // RFC 7871 §6: ADDRESS field is only ceil(sourcePrefixLength/8) octets,
247+ // so /24 writes 3 address bytes (10.11.12), not 4.
224248 const b = Packet . Resource . encode ( query ) ;
225249 assert . deepEqual ( b , Buffer . from ( [
226250 0x00 , 0x00 , 0x29 , 0x02 , 0x00 , 0x00 , 0x00 , 0x00 ,
227- 0x00 , 0x00 , 0x0c , 0x00 , 0x08 , 0x00 , 0x08 , 0x00 ,
228- 0x01 , 0x18 , 0x00 , 0x0a , 0x0b , 0x0c , 0x0d ] ) ) ;
251+ 0x00 , 0x00 , 0x0b , 0x00 , 0x08 , 0x00 , 0x07 , 0x00 ,
252+ 0x01 , 0x18 , 0x00 , 0x0a , 0x0b , 0x0c ] ) ) ;
229253} ) ;
230254
231255test ( 'EDNS#decode' , function ( ) {
@@ -772,6 +796,115 @@ test('Packet.uuid exercises the full 16-bit range with high diversity', function
772796 }
773797} ) ;
774798
799+ test ( 'Name decode rejects a pointer cycle (no infinite loop)' , function ( ) {
800+ // Hand-built packet header (12 bytes) followed by a name that points to
801+ // itself: byte 12 = 0xC0 (pointer high), byte 13 = 0x0C (offset = 12).
802+ // Without cycle detection this would loop forever.
803+ const buf = Buffer . alloc ( 14 ) ;
804+ buf [ 12 ] = 0xC0 ;
805+ buf [ 13 ] = 0x0C ;
806+ const reader = new Packet . Reader ( buf ) ;
807+ reader . offset = 8 * 12 ;
808+ assert . throws ( ( ) => Packet . Name . decode ( reader ) , / p o i n t e r c y c l e / ) ;
809+ } ) ;
810+
811+ test ( 'Name decode rejects a two-step pointer cycle' , function ( ) {
812+ // Two pointers pointing at each other: bytes 12-13 = C0 0E, bytes 14-15 = C0 0C.
813+ const buf = Buffer . alloc ( 16 ) ;
814+ buf [ 12 ] = 0xC0 ; buf [ 13 ] = 0x0E ;
815+ buf [ 14 ] = 0xC0 ; buf [ 15 ] = 0x0C ;
816+ const reader = new Packet . Reader ( buf ) ;
817+ reader . offset = 8 * 12 ;
818+ assert . throws ( ( ) => Packet . Name . decode ( reader ) , / p o i n t e r c y c l e / ) ;
819+ } ) ;
820+
821+ test ( 'Header default constructor initializes ancount/ad/cd' , function ( ) {
822+ const header = new Packet . Header ( ) ;
823+ assert . equal ( header . ancount , 0 ) ;
824+ assert . equal ( header . ad , 0 ) ;
825+ assert . equal ( header . cd , 0 ) ;
826+ } ) ;
827+
828+ test ( 'Header#parse exposes AD and CD bits (RFC 4035)' , function ( ) {
829+ // Second header word with AD=1, CD=1, all other flags zero.
830+ // Layout: qr(1) opcode(4) aa(1) tc(1) rd(1) ra(1) z(1) ad(1) cd(1) rcode(4)
831+ // bits : 0 0000 0 0 0 0 0 1 1 0000 => 0000 0000 0011 0000 = 0x0030
832+ const buf = Buffer . from ( [
833+ 0x00 , 0x01 , // id
834+ 0x00 , 0x30 , // flags: AD=1, CD=1
835+ 0x00 , 0x00 , 0x00 , 0x00 , // counts
836+ 0x00 , 0x00 , 0x00 , 0x00 ,
837+ ] ) ;
838+ const header = Packet . Header . parse ( buf ) ;
839+ assert . equal ( header . z , 0 ) ;
840+ assert . equal ( header . ad , 1 ) ;
841+ assert . equal ( header . cd , 1 ) ;
842+ } ) ;
843+
844+ test ( 'Header#toBuffer round-trips AD and CD bits' , function ( ) {
845+ const header = new Packet . Header ( { id : 0x4242 , ad : 1 , cd : 1 } ) ;
846+ const parsed = Packet . Header . parse ( header . toBuffer ( ) ) ;
847+ assert . equal ( parsed . id , 0x4242 ) ;
848+ assert . equal ( parsed . ad , 1 ) ;
849+ assert . equal ( parsed . cd , 1 ) ;
850+ assert . equal ( parsed . z , 0 ) ;
851+ } ) ;
852+
853+ test ( 'EDNS exposes extendedRcode / version / doFlag' , function ( ) {
854+ const opt = new Packet . Resource . EDNS ( [ ] , { extendedRcode : 16 , version : 0 , doFlag : true } ) ;
855+ assert . equal ( opt . extendedRcode , 16 ) ;
856+ assert . equal ( opt . version , 0 ) ;
857+ assert . equal ( opt . doFlag , true ) ;
858+ // ttl wire encoding: ext rcode in top byte, DO at bit 15 of low half.
859+ assert . equal ( opt . ttl , ( 16 << 24 ) | 0x8000 ) ;
860+ } ) ;
861+
862+ test ( 'EDNS round-trip preserves DO bit and extended RCODE' , function ( ) {
863+ const opt = new Packet . Resource . EDNS ( [ ] , { extendedRcode : 23 , version : 0 , doFlag : true } ) ;
864+ const parsed = Packet . Resource . decode ( Packet . Resource . encode ( opt ) ) ;
865+ assert . equal ( parsed . extendedRcode , 23 ) ;
866+ assert . equal ( parsed . doFlag , true ) ;
867+ } ) ;
868+
869+ test ( 'EDNS udpPayloadSize is configurable (RFC 6891 §6.2.3)' , function ( ) {
870+ const opt = new Packet . Resource . EDNS ( [ ] , { udpPayloadSize : 4096 } ) ;
871+ assert . equal ( opt . class , 4096 ) ;
872+ const parsed = Packet . Resource . decode ( Packet . Resource . encode ( opt ) ) ;
873+ assert . equal ( parsed . class , 4096 ) ;
874+ } ) ;
875+
876+ test ( 'EDNS.ECS#encode truncates IPv4 address to prefix length (RFC 7871)' , function ( ) {
877+ // /8 → 1 octet, /17 → 3 octets (ceil)
878+ for ( const [ cidr , expectedOctets ] of [
879+ [ '10.0.0.0/8' , 1 ] ,
880+ [ '10.20.0.0/16' , 2 ] ,
881+ [ '10.20.30.0/24' , 3 ] ,
882+ [ '10.20.30.0/17' , 3 ] ,
883+ [ '10.20.30.40/32' , 4 ] ,
884+ ] ) {
885+ const query = new Packet . Resource . EDNS ( [ new Packet . Resource . EDNS . ECS ( cidr ) ] ) ;
886+ const buf = Packet . Resource . encode ( query ) ;
887+ // Layout: name(1) type(2) class(2) ttl(4) rdlength(2) optionCode(2)
888+ // optionLength(2) → optionLength sits at offset 13. Address byte count =
889+ // optionLength - 4 (family + src prefix + scope prefix headers).
890+ const optionLength = buf . readUInt16BE ( 13 ) ;
891+ assert . equal ( optionLength - 4 , expectedOctets , `cidr ${ cidr } ` ) ;
892+ }
893+ } ) ;
894+
895+ test ( 'EDNS.ECS#encode supports IPv6 family' , function ( ) {
896+ // family=2 (IPv6), /32 prefix → 4 leading octets of the address.
897+ const ecs = Packet . Resource . EDNS . ECS ( '2001:db8::/32' ) ;
898+ ecs . family = 2 ; // factory currently hard-codes family 1; opt into IPv6
899+ const opt = new Packet . Resource . EDNS ( [ ecs ] ) ;
900+ const buf = Packet . Resource . encode ( opt ) ;
901+ const parsed = Packet . Resource . decode ( buf ) ;
902+ assert . equal ( parsed . rdata [ 0 ] . family , 2 ) ;
903+ assert . equal ( parsed . rdata [ 0 ] . sourcePrefixLength , 32 ) ;
904+ // The decoder pads truncated IPv6 to 8 segments; '2001:db8' followed by 6 zero segments.
905+ assert . equal ( parsed . rdata [ 0 ] . ip , '2001:db8:0:0:0:0:0:0' ) ;
906+ } ) ;
907+
775908test ( 'Packet.parse tolerates multiple questions' , function ( ) {
776909 const request = new Packet ( ) ;
777910 request . header . id = 0x9999 ;
0 commit comments