Commit 90a19f7411

Artem Kolichenkov <artem@kolichenkov.com>
2023-12-16 17:15:51
std.net: add explicit error sets for IP parsing
Inferred errors in switch statements prevented IP address parsing at comptime. Adding explicit error sets fixes it. Closes #18276
1 parent 779b8e2
Changed files (2)
lib
lib/std/net/test.zig
@@ -4,6 +4,28 @@ const net = std.net;
 const mem = std.mem;
 const testing = std.testing;
 
+test "parse and render IP addresses at comptime" {
+    if (builtin.os.tag == .wasi) return error.SkipZigTest;
+    comptime {
+        var ipAddrBuffer: [16]u8 = undefined;
+        // Parses IPv6 at comptime
+        const ipv6addr = net.Address.parseIp("::1", 0) catch unreachable;
+        var ipv6 = std.fmt.bufPrint(ipAddrBuffer[0..], "{}", .{ipv6addr}) catch unreachable;
+        try std.testing.expect(std.mem.eql(u8, "::1", ipv6[1 .. ipv6.len - 3]));
+
+        // Parses IPv4 at comptime
+        const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable;
+        var ipv4 = std.fmt.bufPrint(ipAddrBuffer[0..], "{}", .{ipv4addr}) catch unreachable;
+        try std.testing.expect(std.mem.eql(u8, "127.0.0.1", ipv4[0 .. ipv4.len - 2]));
+
+        // Returns error for invalid IP addresses at comptime
+        try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("::123.123.123.123", 0));
+        try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("127.01.0.1", 0));
+        try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("::123.123.123.123", 0));
+        try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("127.01.0.1", 0));
+    }
+}
+
 test "parse and render IPv6 addresses" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
lib/std/net.zig
@@ -14,6 +14,19 @@ pub const has_unix_sockets = @hasDecl(os.sockaddr, "un") and
     (builtin.target.os.tag != .windows or
     builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false);
 
+pub const IPParseError = error{
+    Overflow,
+    InvalidEnd,
+    InvalidCharacter,
+    Incomplete,
+};
+
+pub const IPv4ParseError = IPParseError || error{NonCanonical};
+
+pub const IPv6ParseError = IPParseError || error{InvalidIpv4Mapping};
+pub const IPv6InterfaceError = os.SocketError || os.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong};
+pub const IPv6ResolveError = IPv6ParseError || IPv6InterfaceError;
+
 pub const Address = extern union {
     any: os.sockaddr,
     in: Ip4Address,
@@ -77,15 +90,15 @@ pub const Address = extern union {
         }
     }
 
-    pub fn parseIp6(buf: []const u8, port: u16) !Address {
+    pub fn parseIp6(buf: []const u8, port: u16) IPv6ParseError!Address {
         return Address{ .in6 = try Ip6Address.parse(buf, port) };
     }
 
-    pub fn resolveIp6(buf: []const u8, port: u16) !Address {
+    pub fn resolveIp6(buf: []const u8, port: u16) IPv6ResolveError!Address {
         return Address{ .in6 = try Ip6Address.resolve(buf, port) };
     }
 
-    pub fn parseIp4(buf: []const u8, port: u16) !Address {
+    pub fn parseIp4(buf: []const u8, port: u16) IPv4ParseError!Address {
         return Address{ .in = try Ip4Address.parse(buf, port) };
     }
 
@@ -198,7 +211,7 @@ pub const Address = extern union {
 pub const Ip4Address = extern struct {
     sa: os.sockaddr.in,
 
-    pub fn parse(buf: []const u8, port: u16) !Ip4Address {
+    pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address {
         var result = Ip4Address{
             .sa = .{
                 .port = mem.nativeToBig(u16, port),
@@ -307,7 +320,7 @@ pub const Ip6Address = extern struct {
     /// Parse a given IPv6 address string into an Address.
     /// Assumes the Scope ID of the address is fully numeric.
     /// For non-numeric addresses, see `resolveIp6`.
-    pub fn parse(buf: []const u8, port: u16) !Ip6Address {
+    pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address {
         var result = Ip6Address{
             .sa = os.sockaddr.in6{
                 .scope_id = 0,
@@ -424,7 +437,7 @@ pub const Ip6Address = extern struct {
         }
     }
 
-    pub fn resolve(buf: []const u8, port: u16) !Ip6Address {
+    pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address {
         // TODO: Unify the implementations of resolveIp6 and parseIp6.
         var result = Ip6Address{
             .sa = os.sockaddr.in6{
@@ -659,7 +672,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
     };
 }
 
-fn if_nametoindex(name: []const u8) !u32 {
+fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
     if (builtin.target.os.tag == .linux) {
         var ifr: os.ifreq = undefined;
         const sockfd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM | os.SOCK.CLOEXEC, 0);