diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index aca24b1c0c..e8a477c5d6 100644 --- a/lib/std/os/bits/darwin.zig +++ b/lib/std/os/bits/darwin.zig @@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006; pub const SO_ERROR = 0x1007; pub const SO_TYPE = 0x1008; +pub const SO_NREAD = 0x1020; +pub const SO_NKE = 0x1021; +pub const SO_NOSIGPIPE = 0x1022; +pub const SO_NOADDRERR = 0x1023; +pub const SO_NWRITE = 0x1024; +pub const SO_REUSESHAREUID = 0x1025; + fn wstatus(x: u32) u32 { return x & 0o177; } diff --git a/lib/std/x.zig b/lib/std/x.zig index df7debf121..02742f29ec 100644 --- a/lib/std/x.zig +++ b/lib/std/x.zig @@ -1 +1,8 @@ -pub const os = @import("x/os/os.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"); +}; diff --git a/lib/std/x/net/TCP.zig b/lib/std/x/net/TCP.zig new file mode 100644 index 0000000000..5e81dbc060 --- /dev/null +++ b/lib/std/x/net/TCP.zig @@ -0,0 +1,399 @@ +const std = @import("../../std.zig"); + +const os = std.os; +const fmt = std.fmt; +const mem = std.mem; +const testing = std.testing; + +const IPv4 = std.x.os.IPv4; +const IPv6 = std.x.os.IPv6; +const Socket = std.x.os.Socket; + +/// A generic TCP socket abstraction. +const TCP = @This(); + +/// A TCP client-address pair. +pub const Connection = struct { + 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 }; + } + + /// Closes the underlying client of the connection. + pub fn deinit(self: TCP.Connection) void { + self.client.deinit(); + } +}; + +/// Possible domains that a TCP client/listener may operate over. +pub const Domain = extern enum(u16) { + ip = os.AF_INET, + ipv6 = os.AF_INET6, +}; + +/// A TCP client. +pub const Client = struct { + socket: Socket, + + /// Opens a new client. + pub fn init(domain: TCP.Domain, flags: u32) !Client { + return Client{ + .socket = try Socket.init( + @enumToInt(domain), + os.SOCK_STREAM | flags, + os.IPPROTO_TCP, + ), + }; + } + + /// Enclose a TCP client over an existing socket. + pub fn from(socket: Socket) Client { + return Client{ .socket = socket }; + } + + /// Closes the client. + pub fn deinit(self: Client) void { + self.socket.deinit(); + } + + /// Shutdown either the read side, write side, or all sides of the client's underlying socket. + pub fn shutdown(self: Client, how: os.ShutdownHow) !void { + return self.socket.shutdown(how); + } + + /// 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); + } + + /// Read data from the socket into the buffer provided. It returns the + /// number of bytes read into the buffer provided. + pub fn read(self: Client, buf: []u8) !usize { + return self.socket.read(buf); + } + + /// Read data from the socket into the buffer provided with a set of flags + /// specified. It returns the number of bytes read into the buffer provided. + pub fn recv(self: Client, buf: []u8, flags: u32) !usize { + return self.socket.recv(buf, flags); + } + + /// Write a buffer of data provided to the socket. It returns the number + /// of bytes that are written to the socket. + pub fn write(self: Client, buf: []const u8) !usize { + return self.socket.write(buf); + } + + /// Writes multiple I/O vectors to the socket. It returns the number + /// of bytes that are written to the socket. + pub fn writev(self: Client, buffers: []const os.iovec_const) !usize { + return self.socket.writev(buffers); + } + + /// Write a buffer of data provided to the socket with a set of flags specified. + /// It returns the number of bytes that are written to the socket. + pub fn send(self: Client, buf: []const u8, flags: u32) !usize { + return self.socket.send(buf, flags); + } + + /// Writes multiple I/O vectors with a prepended message header to the socket + /// with a set of flags specified. It returns the number of bytes that are + /// written to the socket. + pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize { + return self.socket.sendmsg(msg, flags); + } + + /// Query and return the latest cached error on the client's underlying socket. + pub fn getError(self: Client) !void { + return self.socket.getError(); + } + + /// Query the read buffer size of the client's underlying socket. + pub fn getReadBufferSize(self: Client) !u32 { + return self.socket.getReadBufferSize(); + } + + /// Query the write buffer size of the client's underlying socket. + pub fn getWriteBufferSize(self: Client) !u32 { + return self.socket.getWriteBufferSize(); + } + + /// 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); + } + + /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if + /// the host does not support sockets disabling Nagle's algorithm. + pub fn setNoDelay(self: Client, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_NODELAY")) { + const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled))); + return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes); + } + return error.UnsupportedSocketOption; + } + + /// Set the write buffer size of the socket. + pub fn setWriteBufferSize(self: Client, size: u32) !void { + return self.socket.setWriteBufferSize(size); + } + + /// Set the read buffer size of the socket. + pub fn setReadBufferSize(self: Client, size: u32) !void { + return self.socket.setReadBufferSize(size); + } + + /// Set a timeout on the socket that is to occur if no messages are successfully written + /// to its bound destination after a specified number of milliseconds. A subsequent write + /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. + pub fn setWriteTimeout(self: Client, milliseconds: usize) !void { + return self.socket.setWriteTimeout(milliseconds); + } + + /// Set a timeout on the socket that is to occur if no messages are successfully read + /// from its bound destination after a specified number of milliseconds. A subsequent + /// read from the socket will thereafter return `error.WouldBlock` should the timeout be + /// exceeded. + pub fn setReadTimeout(self: Client, milliseconds: usize) !void { + return self.socket.setReadTimeout(milliseconds); + } +}; + +/// A TCP listener. +pub const Listener = struct { + socket: Socket, + + /// Opens a new listener. + pub fn init(domain: TCP.Domain, flags: u32) !Listener { + return Listener{ + .socket = try Socket.init( + @enumToInt(domain), + os.SOCK_STREAM | flags, + os.IPPROTO_TCP, + ), + }; + } + + /// Closes the listener. + pub fn deinit(self: Listener) void { + self.socket.deinit(); + } + + /// Shuts down the underlying listener's socket. The next subsequent call, or + /// a current pending call to accept() after shutdown is called will return + /// an error. + pub fn shutdown(self: Listener) !void { + return self.socket.shutdown(.recv); + } + + /// Binds the listener's socket to an address. + pub fn bind(self: Listener, address: TCP.Address) !void { + return self.socket.bind(TCP.Address, address); + } + + /// Start listening for incoming connections. + pub fn listen(self: Listener, max_backlog_size: u31) !void { + return self.socket.listen(max_backlog_size); + } + + /// 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); + } + + /// Query and return the latest cached error on the listener's underlying socket. + pub fn getError(self: Client) !void { + return self.socket.getError(); + } + + /// 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); + } + + /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if + /// the host does not support sockets listening the same address. + pub fn setReuseAddress(self: Listener, enabled: bool) !void { + return self.socket.setReuseAddress(enabled); + } + + /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if + /// the host does not supports sockets listening on the same port. + pub fn setReusePort(self: Listener, enabled: bool) !void { + return self.socket.setReusePort(enabled); + } + + /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not + /// support TCP Fast Open. + pub fn setFastOpen(self: Listener, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_FASTOPEN")) { + return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; + } + + /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns + /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. + pub fn setQuickACK(self: Listener, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_QUICKACK")) { + return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; + } + + /// Set a timeout on the listener that is to occur if no new incoming connections come in + /// after a specified number of milliseconds. A subsequent accept call to the listener + /// will thereafter return `error.WouldBlock` should the timeout be exceeded. + pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void { + return self.socket.setReadTimeout(milliseconds); + } +}; + +/// 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(); + + try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer b.deinit(); + + testing.expectError(error.WouldBlock, b.connect(binded_address)); + try b.getError(); + + const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer ab.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(); + + try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC); + defer b.deinit(); + + try b.connect(binded_address); + try b.setReadTimeout(1); + + const ab = try a.accept(os.SOCK_CLOEXEC); + defer ab.deinit(); + + var buf: [1]u8 = undefined; + testing.expectError(error.WouldBlock, b.read(&buf)); +} + +test "tcp/listener: bind to unspecified ipv4 address" { + const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); + defer socket.deinit(); + + try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); + try socket.listen(128); + + const address = try socket.getLocalAddress(); + testing.expect(address.getHost() == .v4); +} + +test "tcp/listener: bind to unspecified ipv6 address" { + const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC); + defer socket.deinit(); + + try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0)); + try socket.listen(128); + + const address = try socket.getLocalAddress(); + testing.expect(address.getHost() == .v6); +} diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig index 3f1971a877..96e64c97ef 100644 --- a/lib/std/x/os/Socket.zig +++ b/lib/std/x/os/Socket.zig @@ -2,19 +2,11 @@ const std = @import("../../std.zig"); const os = std.os; const mem = std.mem; -const net = std.net; const time = std.time; -const builtin = std.builtin; -const testing = std.testing; +/// A generic socket abstraction. const Socket = @This(); -/// A socket-address pair. -pub const Connection = struct { - socket: Socket, - address: net.Address, -}; - /// The underlying handle of a socket. fd: os.socket_t, @@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; } +/// Enclose a socket abstraction over an existing socket file descriptor. +pub fn from(fd: os.socket_t) Socket { + return Socket{ .fd = fd }; +} + /// Closes the socket. pub fn deinit(self: Socket) void { os.closeSocket(self.fd); } -/// Shutdown either the read side, or write side, or the entirety of a socket. +/// Shutdown either the read side, write side, or all side of the socket. pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { return os.shutdown(self.fd, how); } /// Binds the socket to an address. -pub fn bind(self: Socket, address: net.Address) !void { - return os.bind(self.fd, &address.any, address.getOsSockLen()); +pub fn bind(self: Socket, comptime Address: type, address: Address) !void { + return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); } /// Start listening for incoming connections on the socket. @@ -44,22 +41,19 @@ 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, address: net.Address) !void { - return os.connect(self.fd, &address.any, address.getOsSockLen()); +pub fn connect(self: Socket, comptime Address: type, address: Address) !void { + return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); } /// Accept a pending incoming connection queued to the kernel backlog /// of the socket. -pub fn accept(self: Socket, flags: u32) !Socket.Connection { - var address: os.sockaddr = undefined; - var address_len: u32 = @sizeOf(os.sockaddr); +pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection { + var address: Address = undefined; + var address_len: u32 = @sizeOf(Address); - const fd = try os.accept(self.fd, &address, &address_len, flags); + const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags); - return Connection{ - .socket = Socket{ .fd = fd }, - .address = net.Address.initPosix(@alignCast(4, &address)), - }; + return Connection.from(.{ .fd = fd }, address); } /// Read data from the socket into the buffer provided. It returns the @@ -100,11 +94,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) !net.Address { - var address: os.sockaddr = undefined; - var address_len: u32 = @sizeOf(os.sockaddr); - try os.getsockname(self.fd, &address, &address_len); - return net.Address.initPosix(@alignCast(4, &address)); +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; } /// Query and return the latest cached error on the socket. @@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void { return error.UnsupportedSocketOption; } -/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support -/// sockets disabling Nagle's algorithm. -pub fn setNoDelay(self: Socket, enabled: bool) !void { - if (comptime @hasDecl(os, "TCP_NODELAY")) { - return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; -} - -/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not -/// support TCP Fast Open. -pub fn setFastOpen(self: Socket, enabled: bool) !void { - if (comptime @hasDecl(os, "TCP_FASTOPEN")) { - return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; -} - -/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns -/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. -pub fn setQuickACK(self: Socket, enabled: bool) !void { - if (comptime @hasDecl(os, "TCP_QUICKACK")) { - return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; -} - /// Set the write buffer size of the socket. pub fn setWriteBufferSize(self: Socket, size: u32) !void { return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); @@ -225,52 +192,3 @@ pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); } - -test { - testing.refAllDecls(@This()); -} - -test "socket/linux: set read timeout of 1 millisecond on blocking socket" { - if (builtin.os.tag != .linux) return error.SkipZigTest; - - const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); - defer a.deinit(); - - try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); - try a.listen(128); - - const binded_address = try a.getLocalAddress(); - - const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); - defer b.deinit(); - - try b.connect(binded_address); - try b.setReadTimeout(1); - - const ab = try a.accept(os.SOCK_CLOEXEC); - defer ab.socket.deinit(); - - var buf: [1]u8 = undefined; - testing.expectError(error.WouldBlock, b.read(&buf)); -} - -test "socket/linux: create non-blocking socket pair" { - if (builtin.os.tag != .linux) return error.SkipZigTest; - - const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); - defer a.deinit(); - - try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); - try a.listen(128); - - const binded_address = try a.getLocalAddress(); - - const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); - defer b.deinit(); - - testing.expectError(error.WouldBlock, b.connect(binded_address)); - try b.getError(); - - const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); - defer ab.socket.deinit(); -} diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig new file mode 100644 index 0000000000..dddb3b73e0 --- /dev/null +++ b/lib/std/x/os/net.zig @@ -0,0 +1,532 @@ +const std = @import("../../std.zig"); + +const os = std.os; +const fmt = std.fmt; +const mem = std.mem; +const math = std.math; +const builtin = std.builtin; +const testing = std.testing; + +/// Resolves a network interface name into a scope/zone ID. It returns +/// an error if either resolution fails, or if the interface name is +/// too long. +pub fn resolveScopeID(name: []const u8) !u32 { + if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong; + + const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0); + defer os.closeSocket(fd); + + var f: os.ifreq = undefined; + mem.copy(u8, &f.ifrn.name, name); + f.ifrn.name[name.len] = 0; + + try os.ioctl_SIOCGIFINDEX(fd, &f); + + return @bitCast(u32, f.ifru.ivalue); +} + +/// An IPv4 address comprised of 4 bytes. +pub const IPv4 = extern struct { + /// Octets of a IPv4 address designating the local host. + pub const localhost_octets = [_]u8{ 127, 0, 0, 1 }; + + /// The IPv4 address of the local host. + pub const localhost: IPv4 = .{ .octets = localhost_octets }; + + /// Octets of an unspecified IPv4 address. + pub const unspecified_octets = [_]u8{0} ** 4; + + /// An unspecified IPv4 address. + pub const unspecified: IPv4 = .{ .octets = unspecified_octets }; + + /// Octets of a broadcast IPv4 address. + pub const broadcast_octets = [_]u8{255} ** 4; + + /// An IPv4 broadcast address. + pub const broadcast: IPv4 = .{ .octets = broadcast_octets }; + + /// The prefix octet pattern of a link-local IPv4 address. + pub const link_local_prefix = [_]u8{ 169, 254 }; + + /// The prefix octet patterns of IPv4 addresses intended for + /// documentation. + pub const documentation_prefixes = [_][]const u8{ + &[_]u8{ 192, 0, 2 }, + &[_]u8{ 198, 51, 100 }, + &[_]u8{ 203, 0, 113 }, + }; + + octets: [4]u8, + + /// Returns whether or not the two addresses are equal to, less than, or + /// greater than each other. + pub fn cmp(self: IPv4, other: IPv4) math.Order { + return mem.order(u8, &self.octets, &other.octets); + } + + /// Returns true if both addresses are semantically equivalent. + pub fn eql(self: IPv4, other: IPv4) bool { + return mem.eql(u8, &self.octets, &other.octets); + } + + /// Returns true if the address is a loopback address. + pub fn isLoopback(self: IPv4) bool { + return self.octets[0] == 127; + } + + /// Returns true if the address is an unspecified IPv4 address. + pub fn isUnspecified(self: IPv4) bool { + return mem.eql(u8, &self.octets, &unspecified_octets); + } + + /// Returns true if the address is a private IPv4 address. + pub fn isPrivate(self: IPv4) bool { + return self.octets[0] == 10 or + (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or + (self.octets[0] == 192 and self.octets[1] == 168); + } + + /// Returns true if the address is a link-local IPv4 address. + pub fn isLinkLocal(self: IPv4) bool { + return mem.startsWith(u8, &self.octets, &link_local_prefix); + } + + /// Returns true if the address is a multicast IPv4 address. + pub fn isMulticast(self: IPv4) bool { + return self.octets[0] >= 224 and self.octets[0] <= 239; + } + + /// Returns true if the address is a IPv4 broadcast address. + pub fn isBroadcast(self: IPv4) bool { + return mem.eql(u8, &self.octets, &broadcast_octets); + } + + /// Returns true if the address is in a range designated for documentation. Refer + /// to IETF RFC 5737 for more details. + pub fn isDocumentation(self: IPv4) bool { + inline for (documentation_prefixes) |prefix| { + if (mem.startsWith(u8, &self.octets, prefix)) { + return true; + } + } + return false; + } + + /// Implements the `std.fmt.format` API. + pub fn format( + self: IPv4, + comptime layout: []const u8, + opts: fmt.FormatOptions, + writer: anytype, + ) !void { + if (comptime layout.len != 0 and layout[0] != 's') { + @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'."); + } + + try fmt.format(writer, "{}.{}.{}.{}", .{ + self.octets[0], + self.octets[1], + self.octets[2], + self.octets[3], + }); + } + + /// Set of possible errors that may encountered when parsing an IPv4 + /// address. + pub const ParseError = error{ + UnexpectedEndOfOctet, + TooManyOctets, + OctetOverflow, + UnexpectedToken, + IncompleteAddress, + }; + + /// Parses an arbitrary IPv4 address. + pub fn parse(buf: []const u8) ParseError!IPv4 { + var octets: [4]u8 = undefined; + var octet: u8 = 0; + + var index: u8 = 0; + var saw_any_digits: bool = false; + + for (buf) |c| { + switch (c) { + '.' => { + if (!saw_any_digits) return error.UnexpectedEndOfOctet; + if (index == 3) return error.TooManyOctets; + octets[index] = octet; + index += 1; + octet = 0; + saw_any_digits = false; + }, + '0'...'9' => { + saw_any_digits = true; + octet = math.mul(u8, octet, 10) catch return error.OctetOverflow; + octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow; + }, + else => return error.UnexpectedToken, + } + } + + if (index == 3 and saw_any_digits) { + octets[index] = octet; + return IPv4{ .octets = octets }; + } + + return error.IncompleteAddress; + } + + /// Maps the address to its IPv6 equivalent. In most cases, you would + /// want to map the address to its IPv6 equivalent rather than directly + /// re-interpreting the address. + pub fn mapToIPv6(self: IPv4) IPv6 { + var octets: [16]u8 = undefined; + mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix); + mem.copy(u8, octets[12..], &self.octets); + return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; + } + + /// Directly re-interprets the address to its IPv6 equivalent. In most + /// cases, you would want to map the address to its IPv6 equivalent rather + /// than directly re-interpreting the address. + pub fn toIPv6(self: IPv4) IPv6 { + var octets: [16]u8 = undefined; + mem.set(u8, octets[0..12], 0); + mem.copy(u8, octets[12..], &self.octets); + return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; + } +}; + +/// 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 { + /// Octets of a IPv6 address designating the local host. + pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; + + /// The IPv6 address of the local host. + pub const localhost: IPv6 = .{ + .octets = localhost_octets, + .scope_id = no_scope_id, + }; + + /// Octets of an unspecified IPv6 address. + pub const unspecified_octets = [_]u8{0} ** 16; + + /// An unspecified IPv6 address. + pub const unspecified: IPv6 = .{ + .octets = unspecified_octets, + .scope_id = no_scope_id, + }; + + /// The prefix of a IPv6 address that is mapped to a IPv4 address. + pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2; + + /// A marker value used to designate an IPv6 address with no + /// associated scope ID. + pub const no_scope_id = math.maxInt(u32); + + octets: [16]u8, + scope_id: u32, + + /// Returns whether or not the two addresses are equal to, less than, or + /// greater than each other. + pub fn cmp(self: IPv6, other: IPv6) math.Order { + return switch (mem.order(u8, self.octets, other.octets)) { + .eq => math.order(self.scope_id, other.scope_id), + else => |order| order, + }; + } + + /// Returns true if both addresses are semantically equivalent. + pub fn eql(self: IPv6, other: IPv6) bool { + return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets); + } + + /// Returns true if the address is an unspecified IPv6 address. + pub fn isUnspecified(self: IPv6) bool { + return mem.eql(u8, &self.octets, &unspecified_octets); + } + + /// Returns true if the address is a loopback address. + pub fn isLoopback(self: IPv6) bool { + return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and + mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 }); + } + + /// Returns true if the address maps to an IPv4 address. + pub fn mapsToIPv4(self: IPv6) bool { + return mem.startsWith(u8, &self.octets, &v4_mapped_prefix); + } + + /// Returns an IPv4 address representative of the address should + /// it the address be mapped to an IPv4 address. It returns null + /// otherwise. + pub fn toIPv4(self: IPv6) ?IPv4 { + if (!self.mapsToIPv4()) return null; + return IPv4{ .octets = self.octets[12..][0..4].* }; + } + + /// Returns true if the address is a multicast IPv6 address. + pub fn isMulticast(self: IPv6) bool { + return self.octets[0] == 0xFF; + } + + /// Returns true if the address is a unicast link local IPv6 address. + pub fn isLinkLocal(self: IPv6) bool { + return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80; + } + + /// Returns true if the address is a deprecated unicast site local + /// IPv6 address. Refer to IETF RFC 3879 for more details as to + /// why they are deprecated. + pub fn isSiteLocal(self: IPv6) bool { + return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0; + } + + /// IPv6 multicast address scopes. + pub const Scope = enum(u8) { + interface = 1, + link = 2, + realm = 3, + admin = 4, + site = 5, + organization = 8, + global = 14, + unknown = 0xFF, + }; + + /// Returns the multicast scope of the address. + pub fn scope(self: IPv6) Scope { + if (!self.isMulticast()) return .unknown; + + return switch (self.octets[0] & 0x0F) { + 1 => .interface, + 2 => .link, + 3 => .realm, + 4 => .admin, + 5 => .site, + 8 => .organization, + 14 => .global, + else => .unknown, + }; + } + + /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the + /// address lower-cased octets, while specifying 'X' or 'S' formats the + /// address using upper-cased ASCII octets. + /// + /// The default specifier is 'x'. + pub fn format( + self: IPv6, + comptime layout: []const u8, + opts: fmt.FormatOptions, + writer: anytype, + ) !void { + comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) { + 'x', 'X' => |specifier| specifier, + 's' => 'x', + 'S' => 'X', + else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."), + }}; + + if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) { + return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{ + 0xFF, + 0xFF, + self.octets[12], + self.octets[13], + self.octets[14], + self.octets[15], + }); + } + + const zero_span = span: { + var i: usize = 0; + while (i < self.octets.len) : (i += 2) { + if (self.octets[i] == 0 and self.octets[i + 1] == 0) break; + } else break :span .{ .from = 0, .to = 0 }; + + const from = i; + + while (i < self.octets.len) : (i += 2) { + if (self.octets[i] != 0 or self.octets[i + 1] != 0) break; + } + + break :span .{ .from = from, .to = i }; + }; + + var i: usize = 0; + while (i != 16) : (i += 2) { + if (zero_span.from != zero_span.to and i == zero_span.from) { + try writer.writeAll("::"); + } else if (i >= zero_span.from and i < zero_span.to) {} else { + if (i != 0 and i != zero_span.to) try writer.writeAll(":"); + + const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1]; + try fmt.formatIntValue(val, specifier, .{}, writer); + } + } + + if (self.scope_id != no_scope_id and self.scope_id != 0) { + try fmt.format(writer, "%{d}", .{self.scope_id}); + } + } + + /// Set of possible errors that may encountered when parsing an IPv6 + /// address. + pub const ParseError = error{ + MalformedV4Mapping, + BadScopeID, + } || IPv4.ParseError; + + /// Parses an arbitrary IPv6 address, including link-local addresses. + pub fn parse(buf: []const u8) ParseError!IPv6 { + if (mem.lastIndexOfScalar(u8, buf, '%')) |index| { + const ip_slice = buf[0..index]; + const scope_id_slice = buf[index + 1 ..]; + + if (scope_id_slice.len == 0) return error.BadScopeID; + + const scope_id: u32 = switch (scope_id_slice[0]) { + '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10), + else => resolveScopeID(scope_id_slice), + } catch return error.BadScopeID; + + return parseWithScopeID(ip_slice, scope_id); + } + + return parseWithScopeID(buf, no_scope_id); + } + + /// Parses an IPv6 address with a pre-specified scope ID. Presumes + /// that the address is not a link-local address. + pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 { + var octets: [16]u8 = undefined; + var octet: u16 = 0; + var tail: [16]u8 = undefined; + + var out: []u8 = &octets; + var index: u8 = 0; + + var saw_any_digits: bool = false; + var abbrv: bool = false; + + for (buf) |c, i| { + switch (c) { + ':' => { + if (!saw_any_digits) { + if (abbrv) return error.UnexpectedToken; + if (i != 0) abbrv = true; + mem.set(u8, out[index..], 0); + out = &tail; + index = 0; + continue; + } + if (index == 14) return error.TooManyOctets; + + out[index] = @truncate(u8, octet >> 8); + index += 1; + out[index] = @truncate(u8, octet); + index += 1; + + octet = 0; + saw_any_digits = false; + }, + '.' => { + if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) { + return error.MalformedV4Mapping; + } + const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; + const v4 = try IPv4.parse(buf[start_index..]); + octets[10] = 0xFF; + octets[11] = 0xFF; + mem.copy(u8, octets[12..], &v4.octets); + + return IPv6{ .octets = octets, .scope_id = scope_id }; + }, + else => { + saw_any_digits = true; + const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken; + octet = math.mul(u16, octet, 16) catch return error.OctetOverflow; + octet = math.add(u16, octet, digit) catch return error.OctetOverflow; + }, + } + } + + if (!saw_any_digits and !abbrv) { + return error.IncompleteAddress; + } + + if (index == 14) { + out[14] = @truncate(u8, octet >> 8); + out[15] = @truncate(u8, octet); + } else { + out[index] = @truncate(u8, octet >> 8); + index += 1; + out[index] = @truncate(u8, octet); + index += 1; + mem.copy(u8, octets[16 - index ..], out[0..index]); + } + + return IPv6{ .octets = octets, .scope_id = scope_id }; + } +}; + +test { + testing.refAllDecls(@This()); +} + +test "ip: convert to and from ipv6" { + try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()}); + testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4()); + + try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()}); + testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4()); + + testing.expect(IPv4.localhost.toIPv6().toIPv4() == null); + try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()}); +} + +test "ipv4: parse & format" { + const cases = [_][]const u8{ + "0.0.0.0", + "255.255.255.255", + "1.2.3.4", + "123.255.0.91", + "127.0.0.1", + }; + + for (cases) |case| { + try testing.expectFmt(case, "{}", .{try IPv4.parse(case)}); + } +} + +test "ipv6: parse & format" { + const inputs = [_][]const u8{ + "FF01:0:0:0:0:0:0:FB", + "FF01::Fb", + "::1", + "::", + "2001:db8::", + "::1234:5678", + "2001:db8::1234:5678", + "::ffff:123.5.123.5", + "FF01::FB%lo", + }; + + const outputs = [_][]const u8{ + "ff01::fb", + "ff01::fb", + "::1", + "::", + "2001:db8::", + "::1234:5678", + "2001:db8::1234:5678", + "::ffff:123.5.123.5", + "ff01::fb%1", + }; + + for (inputs) |input, i| { + try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); + } +} diff --git a/lib/std/x/os/os.zig b/lib/std/x/os/os.zig deleted file mode 100644 index 1d1b8edc7c..0000000000 --- a/lib/std/x/os/os.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("../../std.zig"); - -const testing = std.testing; - -pub const Socket = @import("Socket.zig"); - -test { - testing.refAllDecls(@This()); -}