Commit 13068da43e

lithdew <kenta@lithdew.net>
2021-04-30 14:08:49
x/os, x/net: re-approach `Address`, rename namespace `TCP -> tcp`
Address comments from @ifreund and @MasterQ32 to address unsafeness and ergonomics of the `Address` API. Rename the `TCP` namespace to `tcp` as it does not contain any top-level fields. Fix missing reference to `sockaddr` which was identified by @kprotty in os/bits/linux/arm64.zig.
1 parent 2ab5880
Changed files (7)
lib
lib/std/compress/deflate.zig
@@ -662,14 +662,12 @@ test "lengths overflow" {
     // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)
     // f dy  hlit hdist hclen 16  17  18   0 (18)    x138 (18)    x138 (18)     x39 (16) x6
     // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11
-    const stream = [_]u8{
-        0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110
-    };
+    const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 };
 
     const reader = std.io.fixedBufferStream(&stream).reader();
     var window: [0x8000]u8 = undefined;
     var inflate = inflateStream(reader, &window);
 
     var buf: [1]u8 = undefined;
-    std.testing.expectError(error.InvalidLength, inflate.read(&buf));    
+    std.testing.expectError(error.InvalidLength, inflate.read(&buf));
 }
lib/std/os/bits/linux/arm64.zig
@@ -9,6 +9,7 @@
 const std = @import("../../../std.zig");
 const linux = std.os.linux;
 const socklen_t = linux.socklen_t;
+const sockaddr = linux.sockaddr;
 const iovec = linux.iovec;
 const iovec_const = linux.iovec_const;
 const uid_t = linux.uid_t;
lib/std/x/net/TCP.zig → lib/std/x/net/tcp.zig
@@ -1,3 +1,9 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
 const std = @import("../../std.zig");
 
 const os = std.os;
@@ -10,20 +16,76 @@ const IPv6 = std.x.os.IPv6;
 const Socket = std.x.os.Socket;
 
 /// A generic TCP socket abstraction.
-const TCP = @This();
+const tcp = @This();
+
+/// A union of all eligible types of socket addresses over TCP.
+pub const Address = union(enum) {
+    ipv4: IPv4.Address,
+    ipv6: IPv6.Address,
+
+    /// Instantiate a new address with a IPv4 host and port.
+    pub fn initIPv4(host: IPv4, port: u16) Address {
+        return .{ .ipv4 = .{ .host = host, .port = port } };
+    }
+
+    /// Instantiate a new address with a IPv6 host and port.
+    pub fn initIPv6(host: IPv6, port: u16) Address {
+        return .{ .ipv6 = .{ .host = host, .port = port } };
+    }
+
+    /// Re-interpret a generic socket address into a TCP socket address.
+    pub fn from(address: Socket.Address) tcp.Address {
+        return switch (address) {
+            .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
+            .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
+        };
+    }
+
+    /// Re-interpret a TCP socket address into a generic socket address.
+    pub fn into(self: tcp.Address) Socket.Address {
+        return switch (self) {
+            .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
+            .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
+        };
+    }
+
+    /// Implements the `std.fmt.format` API.
+    pub fn format(
+        self: tcp.Address,
+        comptime layout: []const u8,
+        opts: fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        switch (self) {
+            .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
+            .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
+        }
+    }
+};
 
 /// A TCP client-address pair.
 pub const Connection = struct {
-    client: TCP.Client,
-    address: TCP.Address,
+    client: tcp.Client,
+    address: tcp.Address,
 
     /// Enclose a TCP client and address into a client-address pair.
-    pub fn from(socket: Socket, address: TCP.Address) Connection {
-        return .{ .client = TCP.Client.from(socket), .address = address };
+    pub fn from(conn: Socket.Connection) tcp.Connection {
+        return .{
+            .client = tcp.Client.from(conn.socket),
+            .address = tcp.Address.from(conn.address),
+        };
+    }
+
+    /// Unravel a TCP client-address pair into a socket-address pair.
+    pub fn into(self: tcp.Connection) Socket.Connection {
+        return .{
+            .socket = self.client.socket,
+            .address = self.address.into(),
+        };
     }
 
     /// Closes the underlying client of the connection.
-    pub fn deinit(self: TCP.Connection) void {
+    pub fn deinit(self: tcp.Connection) void {
         self.client.deinit();
     }
 };
@@ -39,7 +101,7 @@ pub const Client = struct {
     socket: Socket,
 
     /// Opens a new client.
-    pub fn init(domain: TCP.Domain, flags: u32) !Client {
+    pub fn init(domain: tcp.Domain, flags: u32) !Client {
         return Client{
             .socket = try Socket.init(
                 @enumToInt(domain),
@@ -65,8 +127,8 @@ pub const Client = struct {
     }
 
     /// Have the client attempt to the connect to an address.
-    pub fn connect(self: Client, address: TCP.Address) !void {
-        return self.socket.connect(TCP.Address, address);
+    pub fn connect(self: Client, address: tcp.Address) !void {
+        return self.socket.connect(address.into());
     }
 
     /// Read data from the socket into the buffer provided. It returns the
@@ -122,8 +184,8 @@ pub const Client = struct {
     }
 
     /// Query the address that the client's socket is locally bounded to.
-    pub fn getLocalAddress(self: Client) !TCP.Address {
-        return self.socket.getLocalAddress(TCP.Address);
+    pub fn getLocalAddress(self: Client) !tcp.Address {
+        return tcp.Address.from(try self.socket.getLocalAddress());
     }
 
     /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
@@ -167,7 +229,7 @@ pub const Listener = struct {
     socket: Socket,
 
     /// Opens a new listener.
-    pub fn init(domain: TCP.Domain, flags: u32) !Listener {
+    pub fn init(domain: tcp.Domain, flags: u32) !Listener {
         return Listener{
             .socket = try Socket.init(
                 @enumToInt(domain),
@@ -190,8 +252,8 @@ pub const Listener = struct {
     }
 
     /// Binds the listener's socket to an address.
-    pub fn bind(self: Listener, address: TCP.Address) !void {
-        return self.socket.bind(TCP.Address, address);
+    pub fn bind(self: Listener, address: tcp.Address) !void {
+        return self.socket.bind(address.into());
     }
 
     /// Start listening for incoming connections.
@@ -201,8 +263,8 @@ pub const Listener = struct {
 
     /// Accept a pending incoming connection queued to the kernel backlog
     /// of the listener's socket.
-    pub fn accept(self: Listener, flags: u32) !TCP.Connection {
-        return self.socket.accept(TCP.Connection, TCP.Address, flags);
+    pub fn accept(self: Listener, flags: u32) !tcp.Connection {
+        return tcp.Connection.from(try self.socket.accept(flags));
     }
 
     /// Query and return the latest cached error on the listener's underlying socket.
@@ -211,8 +273,8 @@ pub const Listener = struct {
     }
 
     /// Query the address that the listener's socket is locally bounded to.
-    pub fn getLocalAddress(self: Listener) !TCP.Address {
-        return self.socket.getLocalAddress(TCP.Address);
+    pub fn getLocalAddress(self: Listener) !tcp.Address {
+        return tcp.Address.from(try self.socket.getLocalAddress());
     }
 
     /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
@@ -253,147 +315,69 @@ pub const Listener = struct {
     }
 };
 
-/// A TCP socket address designated by a host IP and port. A TCP socket
-/// address comprises of 28 bytes. It may freely be used in place of
-/// `sockaddr` when working with socket syscalls.
-///
-/// It is not recommended to touch the fields of an `Address`, but to
-/// instead make use of its available accessor methods.
-pub const Address = extern struct {
-    family: u16,
-    port: u16,
-    host: extern union {
-        ipv4: extern struct {
-            address: IPv4,
-        },
-        ipv6: extern struct {
-            flow_info: u32 = 0,
-            address: IPv6,
-        },
-    },
-
-    /// Instantiate a new TCP address with a IPv4 host and port.
-    pub fn initIPv4(host: IPv4, port: u16) Address {
-        return Address{
-            .family = os.AF_INET,
-            .port = mem.nativeToBig(u16, port),
-            .host = .{
-                .ipv4 = .{
-                    .address = host,
-                },
-            },
-        };
-    }
-
-    /// Instantiate a new TCP address with a IPv6 host and port.
-    pub fn initIPv6(host: IPv6, port: u16) Address {
-        return Address{
-            .family = os.AF_INET6,
-            .port = mem.nativeToBig(u16, port),
-            .host = .{
-                .ipv6 = .{
-                    .address = host,
-                },
-            },
-        };
-    }
-
-    /// Extract the host of the address.
-    pub fn getHost(self: Address) union(enum) { v4: IPv4, v6: IPv6 } {
-        return switch (self.family) {
-            os.AF_INET => .{ .v4 = self.host.ipv4.address },
-            os.AF_INET6 => .{ .v6 = self.host.ipv6.address },
-            else => unreachable,
-        };
-    }
-
-    /// Extract the port of the address.
-    pub fn getPort(self: Address) u16 {
-        return mem.nativeToBig(u16, self.port);
-    }
-
-    /// Set the port of the address.
-    pub fn setPort(self: *Address, port: u16) void {
-        self.port = mem.nativeToBig(u16, port);
-    }
-
-    /// Implements the `std.fmt.format` API.
-    pub fn format(
-        self: Address,
-        comptime layout: []const u8,
-        opts: fmt.FormatOptions,
-        writer: anytype,
-    ) !void {
-        switch (self.getHost()) {
-            .v4 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
-            .v6 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
-        }
-    }
-};
-
 test {
     testing.refAllDecls(@This());
 }
 
 test "tcp: create non-blocking pair" {
-    const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
-    defer a.deinit();
+    const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer listener.deinit();
 
-    try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
-    try a.listen(128);
+    try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
+    try listener.listen(128);
 
-    const binded_address = try a.getLocalAddress();
+    const binded_address = try listener.getLocalAddress();
 
-    const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
-    defer b.deinit();
+    const client = try tcp.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer client.deinit();
 
-    testing.expectError(error.WouldBlock, b.connect(binded_address));
-    try b.getError();
+    testing.expectError(error.WouldBlock, client.connect(binded_address));
+    try client.getError();
 
-    const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
-    defer ab.deinit();
+    const conn = try listener.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer conn.deinit();
 }
 
 test "tcp/client: set read timeout of 1 millisecond on blocking client" {
-    const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
-    defer a.deinit();
+    const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
+    defer listener.deinit();
 
-    try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
-    try a.listen(128);
+    try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
+    try listener.listen(128);
 
-    const binded_address = try a.getLocalAddress();
+    const binded_address = try listener.getLocalAddress();
 
-    const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC);
-    defer b.deinit();
+    const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
+    defer client.deinit();
 
-    try b.connect(binded_address);
-    try b.setReadTimeout(1);
+    try client.connect(binded_address);
+    try client.setReadTimeout(1);
 
-    const ab = try a.accept(os.SOCK_CLOEXEC);
-    defer ab.deinit();
+    const conn = try listener.accept(os.SOCK_CLOEXEC);
+    defer conn.deinit();
 
     var buf: [1]u8 = undefined;
-    testing.expectError(error.WouldBlock, b.read(&buf));
+    testing.expectError(error.WouldBlock, client.read(&buf));
 }
 
 test "tcp/listener: bind to unspecified ipv4 address" {
-    const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
-    defer socket.deinit();
+    const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
+    defer listener.deinit();
 
-    try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
-    try socket.listen(128);
+    try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
+    try listener.listen(128);
 
-    const address = try socket.getLocalAddress();
-    testing.expect(address.getHost() == .v4);
+    const address = try listener.getLocalAddress();
+    testing.expect(address == .ipv4);
 }
 
 test "tcp/listener: bind to unspecified ipv6 address" {
-    const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC);
-    defer socket.deinit();
+    const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC);
+    defer listener.deinit();
 
-    try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0));
-    try socket.listen(128);
+    try listener.bind(tcp.Address.initIPv6(IPv6.unspecified, 0));
+    try listener.listen(128);
 
-    const address = try socket.getLocalAddress();
-    testing.expect(address.getHost() == .v6);
+    const address = try listener.getLocalAddress();
+    testing.expect(address == .ipv6);
 }
lib/std/x/os/net.zig
@@ -1,3 +1,9 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
 const std = @import("../../std.zig");
 
 const os = std.os;
@@ -27,6 +33,12 @@ pub fn resolveScopeID(name: []const u8) !u32 {
 
 /// An IPv4 address comprised of 4 bytes.
 pub const IPv4 = extern struct {
+    /// A IPv4 host-port pair.
+    pub const Address = extern struct {
+        host: IPv4,
+        port: u16,
+    };
+
     /// Octets of a IPv4 address designating the local host.
     pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
 
@@ -200,6 +212,12 @@ pub const IPv4 = extern struct {
 /// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
 /// for a scope ID; cumulatively summing to 20 bytes in total.
 pub const IPv6 = extern struct {
+    /// A IPv6 host-port pair.
+    pub const Address = extern struct {
+        host: IPv6,
+        port: u16,
+    };
+
     /// Octets of a IPv6 address designating the local host.
     pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
 
lib/std/x/os/Socket.zig
@@ -1,4 +1,11 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
 const std = @import("../../std.zig");
+const net = @import("net.zig");
 
 const os = std.os;
 const mem = std.mem;
@@ -7,6 +14,98 @@ const time = std.time;
 /// A generic socket abstraction.
 const Socket = @This();
 
+/// A socket-address pair.
+pub const Connection = struct {
+    socket: Socket,
+    address: Socket.Address,
+
+    /// Enclose a socket and address into a socket-address pair.
+    pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
+        return .{ .socket = socket, .address = address };
+    }
+};
+
+/// A generic socket address abstraction. It is safe to directly access and modify
+/// the fields of a `Socket.Address`.
+pub const Address = union(enum) {
+    ipv4: net.IPv4.Address,
+    ipv6: net.IPv6.Address,
+
+    /// Instantiate a new address with a IPv4 host and port.
+    pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
+        return .{ .ipv4 = .{ .host = host, .port = port } };
+    }
+
+    /// Instantiate a new address with a IPv6 host and port.
+    pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
+        return .{ .ipv6 = .{ .host = host, .port = port } };
+    }
+
+    /// Parses a `sockaddr` into a generic socket address.
+    pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
+        switch (address.family) {
+            os.AF_INET => {
+                const info = @ptrCast(*const os.sockaddr_in, address);
+                const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
+                const port = mem.bigToNative(u16, info.port);
+                return Socket.Address.initIPv4(host, port);
+            },
+            os.AF_INET6 => {
+                const info = @ptrCast(*const os.sockaddr_in6, address);
+                const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
+                const port = mem.bigToNative(u16, info.port);
+                return Socket.Address.initIPv6(host, port);
+            },
+            else => unreachable,
+        }
+    }
+
+    /// Encodes a generic socket address into an extern union that may be reliably
+    /// casted into a `sockaddr` which may be passed into socket syscalls.
+    pub fn toNative(self: Socket.Address) extern union {
+        ipv4: os.sockaddr_in,
+        ipv6: os.sockaddr_in6,
+    } {
+        return switch (self) {
+            .ipv4 => |address| .{
+                .ipv4 = .{
+                    .addr = @bitCast(u32, address.host.octets),
+                    .port = mem.nativeToBig(u16, address.port),
+                },
+            },
+            .ipv6 => |address| .{
+                .ipv6 = .{
+                    .addr = address.host.octets,
+                    .port = mem.nativeToBig(u16, address.port),
+                    .scope_id = address.host.scope_id,
+                    .flowinfo = 0,
+                },
+            },
+        };
+    }
+
+    /// Returns the number of bytes that make up the `sockaddr` equivalent to the address. 
+    pub fn getNativeSize(self: Socket.Address) u32 {
+        return switch (self) {
+            .ipv4 => @sizeOf(os.sockaddr_in),
+            .ipv6 => @sizeOf(os.sockaddr_in6),
+        };
+    }
+
+    /// Implements the `std.fmt.format` API.
+    pub fn format(
+        self: Socket.Address,
+        comptime layout: []const u8,
+        opts: fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        switch (self) {
+            .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
+            .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
+        }
+    }
+};
+
 /// The underlying handle of a socket.
 fd: os.socket_t,
 
@@ -31,8 +130,8 @@ pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
 }
 
 /// Binds the socket to an address.
-pub fn bind(self: Socket, comptime Address: type, address: Address) !void {
-    return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
+pub fn bind(self: Socket, address: Socket.Address) !void {
+    return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
 }
 
 /// Start listening for incoming connections on the socket.
@@ -41,19 +140,20 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
 }
 
 /// Have the socket attempt to the connect to an address.
-pub fn connect(self: Socket, comptime Address: type, address: Address) !void {
-    return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
+pub fn connect(self: Socket, address: Socket.Address) !void {
+    return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
 }
 
 /// Accept a pending incoming connection queued to the kernel backlog
 /// of the socket.
-pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection {
-    var address: Address = undefined;
-    var address_len: u32 = @sizeOf(Address);
+pub fn accept(self: Socket, flags: u32) !Socket.Connection {
+    var address: os.sockaddr = undefined;
+    var address_len: u32 = @sizeOf(os.sockaddr);
 
-    const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags);
+    const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
+    const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
 
-    return Connection.from(.{ .fd = fd }, address);
+    return Socket.Connection.from(socket, socket_address);
 }
 
 /// Read data from the socket into the buffer provided. It returns the
@@ -94,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
 }
 
 /// Query the address that the socket is locally bounded to.
-pub fn getLocalAddress(self: Socket, comptime Address: type) !Address {
-    var address: Address = undefined;
-    var address_len: u32 = @sizeOf(Address);
-    try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
-    return address;
+pub fn getLocalAddress(self: Socket) !Socket.Address {
+    var address: os.sockaddr = undefined;
+    var address_len: u32 = @sizeOf(os.sockaddr);
+    try os.getsockname(self.fd, &address, &address_len);
+    return Socket.Address.fromNative(@alignCast(4, &address));
 }
 
 /// Query and return the latest cached error on the socket.
lib/std/builtin.zig
@@ -150,23 +150,7 @@ pub const Mode = enum {
 
 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
-pub const CallingConvention = enum {
-    Unspecified,
-    C,
-    Naked,
-    Async,
-    Inline,
-    Interrupt,
-    Signal,
-    Stdcall,
-    Fastcall,
-    Vectorcall,
-    Thiscall,
-    APCS,
-    AAPCS,
-    AAPCSVFP,
-    SysV
-};
+pub const CallingConvention = enum { Unspecified, C, Naked, Async, Inline, Interrupt, Signal, Stdcall, Fastcall, Vectorcall, Thiscall, APCS, AAPCS, AAPCSVFP, SysV };
 
 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
lib/std/x.zig
@@ -1,8 +1,22 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
+const std = @import("std.zig");
+
 pub const os = struct {
     pub const Socket = @import("x/os/Socket.zig");
     pub usingnamespace @import("x/os/net.zig");
 };
 
 pub const net = struct {
-    pub const TCP = @import("x/net/TCP.zig");
+    pub const tcp = @import("x/net/tcp.zig");
 };
+
+test {
+    inline for (.{ os, net }) |module| {
+        std.testing.refAllDecls(module);
+    }
+}