commit 0e3930bb5a92a60de6faad4c348f062a7a420eca (tree)
parent 5622f9382c4f54d6d2cc267590dda5352718ef60
Author: Andrew Kelley <andrew@ziglang.org>
Date: Mon, 3 May 2021 17:38:10 -0400
Merge pull request #8649 from lithdew/master
x/os, x/net: layout tcp, ipv4/ipv6, and socket abstractions
Diffstat:
13 files changed, 1163 insertions(+), 115 deletions(-)
diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig
@@ -165,7 +165,7 @@ pub const CallingConvention = enum {
APCS,
AAPCS,
AAPCSVFP,
- SysV
+ SysV,
};
/// This data structure is used by the Zig language code generation and
diff --git a/lib/std/compress/deflate.zig b/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));
}
diff --git 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/os/bits/linux.zig b/lib/std/os/bits/linux.zig
@@ -32,6 +32,7 @@ pub usingnamespace @import("linux/prctl.zig");
pub usingnamespace @import("linux/securebits.zig");
const is_mips = builtin.arch.isMIPS();
+const is_ppc = builtin.arch.isPPC();
const is_ppc64 = builtin.arch.isPPC64();
const is_sparc = builtin.arch.isSPARC();
@@ -458,7 +459,39 @@ pub const AF_QIPCRTR = PF_QIPCRTR;
pub const AF_SMC = PF_SMC;
pub const AF_MAX = PF_MAX;
-pub usingnamespace if (!is_mips)
+pub usingnamespace if (is_mips)
+ struct {}
+else if (is_ppc or is_ppc64)
+ struct {
+ pub const SO_DEBUG = 1;
+ pub const SO_REUSEADDR = 2;
+ pub const SO_TYPE = 3;
+ pub const SO_ERROR = 4;
+ pub const SO_DONTROUTE = 5;
+ pub const SO_BROADCAST = 6;
+ pub const SO_SNDBUF = 7;
+ pub const SO_RCVBUF = 8;
+ pub const SO_KEEPALIVE = 9;
+ pub const SO_OOBINLINE = 10;
+ pub const SO_NO_CHECK = 11;
+ pub const SO_PRIORITY = 12;
+ pub const SO_LINGER = 13;
+ pub const SO_BSDCOMPAT = 14;
+ pub const SO_REUSEPORT = 15;
+ pub const SO_RCVLOWAT = 16;
+ pub const SO_SNDLOWAT = 17;
+ pub const SO_RCVTIMEO = 18;
+ pub const SO_SNDTIMEO = 19;
+ pub const SO_PASSCRED = 20;
+ pub const SO_PEERCRED = 21;
+ pub const SO_ACCEPTCONN = 30;
+ pub const SO_PEERSEC = 31;
+ pub const SO_SNDBUFFORCE = 32;
+ pub const SO_RCVBUFFORCE = 33;
+ pub const SO_PROTOCOL = 38;
+ pub const SO_DOMAIN = 39;
+ }
+else
struct {
pub const SO_DEBUG = 1;
pub const SO_REUSEADDR = 2;
@@ -487,9 +520,7 @@ pub usingnamespace if (!is_mips)
pub const SO_RCVBUFFORCE = 33;
pub const SO_PROTOCOL = 38;
pub const SO_DOMAIN = 39;
- }
-else
- struct {};
+ };
pub const SO_SECURITY_AUTHENTICATION = 22;
pub const SO_SECURITY_ENCRYPTION_TRANSPORT = 23;
diff --git a/lib/std/os/bits/linux/arm64.zig b/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;
diff --git a/lib/std/os/bits/linux/riscv64.zig b/lib/std/os/bits/linux/riscv64.zig
@@ -376,6 +376,11 @@ pub const timespec = extern struct {
tv_nsec: isize,
};
+pub const timeval = extern struct {
+ tv_sec: time_t,
+ tv_usec: i64,
+};
+
pub const Flock = extern struct {
l_type: i16,
l_whence: i16,
diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig
@@ -22,6 +22,11 @@ pub const timespec = extern struct {
tv_nsec: c_long,
};
+pub const timeval = extern struct {
+ tv_sec: c_long,
+ tv_usec: c_long,
+};
+
pub const sig_atomic_t = c_int;
/// maximum signal number + 1
diff --git a/lib/std/x.zig b/lib/std/x.zig
@@ -1 +1,23 @@
-pub const os = @import("x/os/os.zig");
+// 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 ip = @import("x/net/ip.zig");
+ pub const tcp = @import("x/net/tcp.zig");
+};
+
+test {
+ inline for (.{ os, net }) |module| {
+ std.testing.refAllDecls(module);
+ }
+}
diff --git a/lib/std/x/net/ip.zig b/lib/std/x/net/ip.zig
@@ -0,0 +1,59 @@
+// 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 IPv4 = std.x.os.IPv4;
+const IPv6 = std.x.os.IPv6;
+const Socket = std.x.os.Socket;
+
+/// A generic IP abstraction.
+const ip = @This();
+
+/// A union of all eligible types of IP addresses.
+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 an IP address.
+ pub fn from(address: Socket.Address) ip.Address {
+ return switch (address) {
+ .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
+ .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
+ };
+ }
+
+ /// Re-interpret an IP address into a generic socket address.
+ pub fn into(self: ip.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: ip.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 }),
+ }
+ }
+};
diff --git a/lib/std/x/net/tcp.zig b/lib/std/x/net/tcp.zig
@@ -0,0 +1,344 @@
+// 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;
+const ip = std.x.net.ip;
+
+const fmt = std.fmt;
+const mem = std.mem;
+const builtin = std.builtin;
+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: ip.Address,
+
+ /// Enclose a TCP client and address into a client-address pair.
+ pub fn from(conn: Socket.Connection) tcp.Connection {
+ return .{
+ .client = tcp.Client.from(conn.socket),
+ .address = ip.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 {
+ 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: ip.Address) !void {
+ return self.socket.connect(address.into());
+ }
+
+ /// 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) !ip.Address {
+ return ip.Address.from(try self.socket.getLocalAddress());
+ }
+
+ /// 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: ip.Address) !void {
+ return self.socket.bind(address.into());
+ }
+
+ /// 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 tcp.Connection.from(try self.socket.accept(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) !ip.Address {
+ return ip.Address.from(try self.socket.getLocalAddress());
+ }
+
+ /// 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);
+ }
+};
+
+test "tcp: create client/listener pair" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
+ defer listener.deinit();
+
+ try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
+ try listener.listen(128);
+
+ const binded_address = try listener.getLocalAddress();
+
+ const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
+ defer client.deinit();
+
+ try client.connect(binded_address);
+
+ const conn = try listener.accept(os.SOCK_CLOEXEC);
+ defer conn.deinit();
+}
+
+test "tcp/client: set read timeout of 1 millisecond on blocking client" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
+ defer listener.deinit();
+
+ try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
+ try listener.listen(128);
+
+ const binded_address = try listener.getLocalAddress();
+
+ const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
+ defer client.deinit();
+
+ try client.connect(binded_address);
+ try client.setReadTimeout(1);
+
+ const conn = try listener.accept(os.SOCK_CLOEXEC);
+ defer conn.deinit();
+
+ var buf: [1]u8 = undefined;
+ testing.expectError(error.WouldBlock, client.read(&buf));
+}
+
+test "tcp/listener: bind to unspecified ipv4 address" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
+ defer listener.deinit();
+
+ try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
+ try listener.listen(128);
+
+ const address = try listener.getLocalAddress();
+ testing.expect(address == .ipv4);
+}
+
+test "tcp/listener: bind to unspecified ipv6 address" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC);
+ defer listener.deinit();
+
+ try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0));
+ try listener.listen(128);
+
+ const address = try listener.getLocalAddress();
+ testing.expect(address == .ipv6);
+}
diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig
@@ -1,18 +1,109 @@
+// 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;
-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,
+ 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.
@@ -23,19 +114,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, 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.
@@ -44,8 +140,8 @@ 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, 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
@@ -54,12 +150,10 @@ 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, &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{
- .socket = Socket{ .fd = fd },
- .address = net.Address.initPosix(@alignCast(4, &address)),
- };
+ return Socket.Connection.from(socket, socket_address);
}
/// Read data from the socket into the buffer provided. It returns the
@@ -100,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) !net.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 net.Address.initPosix(@alignCast(4, &address));
+ return Socket.Address.fromNative(@alignCast(4, &address));
}
/// Query and return the latest cached error on the socket.
@@ -164,33 +258,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));
@@ -206,8 +273,8 @@ pub fn setReadBufferSize(self: Socket, size: u32) !void {
/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
- .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),
- .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),
+ .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
+ .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
@@ -219,58 +286,9 @@ pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
/// exceeded.
pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
- .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),
- .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),
+ .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
+ .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
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
@@ -0,0 +1,567 @@
+// 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;
+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 (comptime @hasDecl(os, "IFNAMESIZE")) {
+ 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);
+ }
+ return error.Unsupported;
+}
+
+/// 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 };
+
+ /// 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 {
+ /// 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};
+
+ /// 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",
+ };
+
+ const outputs = [_][]const u8{
+ "ff01::fb",
+ "ff01::fb",
+ "::1",
+ "::",
+ "2001:db8::",
+ "::1234:5678",
+ "2001:db8::1234:5678",
+ "::ffff:123.5.123.5",
+ };
+
+ for (inputs) |input, i| {
+ try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
+ }
+}
+
+test "ipv6: parse & format addresses with scope ids" {
+ if (!@hasDecl(os, "IFNAMESIZE")) return error.SkipZigTest;
+
+ const inputs = [_][]const u8{
+ "FF01::FB%lo",
+ };
+
+ const outputs = [_][]const u8{
+ "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
@@ -1,9 +0,0 @@
-const std = @import("../../std.zig");
-
-const testing = std.testing;
-
-pub const Socket = @import("Socket.zig");
-
-test {
- testing.refAllDecls(@This());
-}