@@ -462,18 +462,34 @@ pub const Ip6Address = struct {
462462 overflow : usize ,
463463 };
464464
465- pub fn parse (text : []const u8 ) Parsed {
465+ pub fn parse (text_in : []const u8 ) Parsed {
466+ var text : []const u8 = text_in ; // so we can alias v4_amended if needed
466467 if (text .len < 2 ) return .incomplete ;
467- const ip4_prefix = "::ffff:" ;
468- if (std .ascii .startsWithIgnoreCase (text , ip4_prefix )) {
469- const parsed = Ip4Address .parse (text [ip4_prefix .len .. ], 0 ) catch
470- return .{ .invalid_ip4_mapping = ip4_prefix .len };
471- const b = parsed .bytes ;
472- return .{ .success = .{
473- .bytes = .{ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xff , 0xff , b [0 ], b [1 ], b [2 ], b [3 ] },
474- .interface_name = null ,
475- } };
468+
469+ // Pre-processing for trailing :IPv4 - If there is no "%iface", and
470+ // the part after the last ':' has a '.', parse it as ipv4 and
471+ // convert to IPv6 ASCII text in v4_amended as the new "text"
472+ var v4_amended : [(8 * 4 ) + 7 ]u8 = undefined ;
473+ if (std .mem .findScalar (u8 , text , '%' ) == null ) {
474+ const first_parts , const last_part = std .mem .cutScalarLast (u8 , text , ':' ) orelse
475+ return .incomplete ;
476+ if (std .mem .findScalar (u8 , last_part , '.' )) | _ | {
477+ if (first_parts .len > (6 * 4 ) + 5 ) // hhhh:hhhh:hhhh:hhhh:hhhh:hhhh
478+ return .{ .invalid_ip4_mapping = first_parts .len };
479+ const parsed = Ip4Address .parse (last_part , 0 ) catch
480+ return .{ .invalid_ip4_mapping = first_parts .len + 1 };
481+ @memcpy (v4_amended [0.. first_parts .len ], first_parts );
482+ const v4_part = v4_amended [first_parts .len .. ][0.. 10];
483+ v4_part [0 ] = ':' ;
484+ @memcpy (v4_part [1.. 3], & std .fmt .hex (parsed .bytes [0 ]));
485+ @memcpy (v4_part [3.. 5], & std .fmt .hex (parsed .bytes [1 ]));
486+ v4_part [5 ] = ':' ;
487+ @memcpy (v4_part [6.. 8], & std .fmt .hex (parsed .bytes [2 ]));
488+ @memcpy (v4_part [8.. 10], & std .fmt .hex (parsed .bytes [3 ]));
489+ text = v4_amended [0 .. first_parts .len + 10 ];
490+ }
476491 }
492+
477493 // Has to be u16 elements to handle 3-digit hex numbers from compression.
478494 var parts : [8 ]u16 = @splat (0 );
479495 var parts_i : u8 = 0 ;
@@ -586,66 +602,67 @@ pub const Ip6Address = struct {
586602
587603 pub fn format (u : * const Unresolved , w : * Io.Writer ) Io.Writer.Error ! void {
588604 const bytes = & u .bytes ;
589- if (std .mem .eql (u8 , bytes [0.. 12], &[_ ]u8 { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xff , 0xff })) {
590- try w .print ("::ffff:{d}.{d}.{d}.{d}" , .{ bytes [12 ], bytes [13 ], bytes [14 ], bytes [15 ] });
591- } else {
592- const parts : [8 ]u16 = .{
593- std .mem .readInt (u16 , bytes [0.. 2], .big ),
594- std .mem .readInt (u16 , bytes [2.. 4], .big ),
595- std .mem .readInt (u16 , bytes [4.. 6], .big ),
596- std .mem .readInt (u16 , bytes [6.. 8], .big ),
597- std .mem .readInt (u16 , bytes [8.. 10], .big ),
598- std .mem .readInt (u16 , bytes [10.. 12], .big ),
599- std .mem .readInt (u16 , bytes [12.. 14], .big ),
600- std .mem .readInt (u16 , bytes [14.. 16], .big ),
601- };
602-
603- // Find the longest zero run
604- var longest_start : usize = 8 ;
605- var longest_len : usize = 0 ;
606- var current_start : usize = 0 ;
607- var current_len : usize = 0 ;
608-
609- for (parts , 0.. ) | part , i | {
610- if (part == 0 ) {
611- if (current_len == 0 ) {
612- current_start = i ;
613- }
614- current_len += 1 ;
615- if (current_len > longest_len ) {
616- longest_start = current_start ;
617- longest_len = current_len ;
618- }
619- } else {
620- current_len = 0 ;
621- }
622- }
605+ if (std .mem .eql (u8 , bytes [0.. 12], &[_ ]u8 { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xff , 0xff }))
606+ return w .print ("::ffff:{d}.{d}.{d}.{d}" , .{ bytes [12 ], bytes [13 ], bytes [14 ], bytes [15 ] });
607+ if (std .mem .eql (u8 , bytes [0.. 12], &[_ ]u8 { 0 , 0x64 , 0xff , 0x9b , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }))
608+ return w .print ("64:ff9b::{d}.{d}.{d}.{d}" , .{ bytes [12 ], bytes [13 ], bytes [14 ], bytes [15 ] });
609+
610+ const parts : [8 ]u16 = .{
611+ std .mem .readInt (u16 , bytes [0.. 2], .big ),
612+ std .mem .readInt (u16 , bytes [2.. 4], .big ),
613+ std .mem .readInt (u16 , bytes [4.. 6], .big ),
614+ std .mem .readInt (u16 , bytes [6.. 8], .big ),
615+ std .mem .readInt (u16 , bytes [8.. 10], .big ),
616+ std .mem .readInt (u16 , bytes [10.. 12], .big ),
617+ std .mem .readInt (u16 , bytes [12.. 14], .big ),
618+ std .mem .readInt (u16 , bytes [14.. 16], .big ),
619+ };
623620
624- // Only compress if the longest zero run is 2 or more
625- if ( longest_len < 2 ) {
626- longest_start = 8 ;
627- longest_len = 0 ;
628- }
621+ // Find the longest zero run
622+ var longest_start : usize = 8 ;
623+ var longest_len : usize = 0 ;
624+ var current_start : usize = 0 ;
625+ var current_len : usize = 0 ;
629626
630- var i : usize = 0 ;
631- var abbrv = false ;
632- while (i < parts .len ) : (i += 1 ) {
633- if (i == longest_start ) {
634- // Emit "::" for the longest zero run
635- if (! abbrv ) {
636- try w .writeAll (if (i == 0 ) "::" else ":" );
637- abbrv = true ;
638- }
639- i += longest_len - 1 ; // Skip the compressed range
640- continue ;
627+ for (parts , 0.. ) | part , i | {
628+ if (part == 0 ) {
629+ if (current_len == 0 ) {
630+ current_start = i ;
641631 }
642- if (abbrv ) {
643- abbrv = false ;
632+ current_len += 1 ;
633+ if (current_len > longest_len ) {
634+ longest_start = current_start ;
635+ longest_len = current_len ;
644636 }
645- try w .print ("{x}" , .{parts [i ]});
646- if (i != parts .len - 1 ) {
647- try w .writeAll (":" );
637+ } else {
638+ current_len = 0 ;
639+ }
640+ }
641+
642+ // Only compress if the longest zero run is 2 or more
643+ if (longest_len < 2 ) {
644+ longest_start = 8 ;
645+ longest_len = 0 ;
646+ }
647+
648+ var i : usize = 0 ;
649+ var abbrv = false ;
650+ while (i < parts .len ) : (i += 1 ) {
651+ if (i == longest_start ) {
652+ // Emit "::" for the longest zero run
653+ if (! abbrv ) {
654+ try w .writeAll (if (i == 0 ) "::" else ":" );
655+ abbrv = true ;
648656 }
657+ i += longest_len - 1 ; // Skip the compressed range
658+ continue ;
659+ }
660+ if (abbrv ) {
661+ abbrv = false ;
662+ }
663+ try w .print ("{x}" , .{parts [i ]});
664+ if (i != parts .len - 1 ) {
665+ try w .writeAll (":" );
649666 }
650667 }
651668 if (u .interface_name ) | n | try w .print ("%{s}" , .{n });
@@ -1354,6 +1371,12 @@ test "parsing IPv6 addresses" {
13541371 try testIp6Parse ("fe80::abcd:ef12%3" );
13551372 try testIp6Parse ("ff02::" );
13561373 try testIp6Parse ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" );
1374+ try testIp6Parse ("::ffff:192.0.2.1" ); // IPv4 Mapped
1375+ try testIp6Parse ("64:ff9b::192.0.2.1" ); // RFC 6052 Well-Known Prefix
1376+ try testIp6Parse ("fe80::e0e:76ff:fed4:cf22%iface.123" ); // edge case for :ipv4 parsing
1377+ try testIp6ParseTransform ("::c000:201" , "::192.0.2.1" ); // Deprecated "IPv4 Compatible"
1378+ try testIp6ParseTransform ("2001:db8::c000:201" , "2001:db8::192.0.2.1" ); // arbitrary prefix
1379+ try testIp6ParseTransform ("2001:db8::201" , "2001:db8::0.0.2.1" ); // Compression+:IPv4 edge case
13571380}
13581381
13591382fn testIp6Parse (input : []const u8 ) ! void {
0 commit comments