remove the experimental std.x namespace

Playtime is over. I'm working on networking now.
This commit is contained in:
Andrew Kelley 2022-12-11 15:35:15 -07:00
parent ebcfc86bb9
commit cd0d514643
22 changed files with 143 additions and 3448 deletions

View File

@ -206,7 +206,7 @@ pub extern "c" fn sendto(
dest_addr: ?*const c.sockaddr,
addrlen: c.socklen_t,
) isize;
pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const std.x.os.Socket.Message, flags: c_int) isize;
pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const c.msghdr_const, flags: u32) isize;
pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize;
pub extern "c" fn recvfrom(
@ -217,7 +217,7 @@ pub extern "c" fn recvfrom(
noalias src_addr: ?*c.sockaddr,
noalias addrlen: ?*c.socklen_t,
) isize;
pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *std.x.os.Socket.Message, flags: c_int) isize;
pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *c.msghdr, flags: u32) isize;
pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int;
pub extern "c" fn getdirentries(fd: c.fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;

View File

@ -1007,7 +1007,16 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [126]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),
family: sa_family_t = AF.INET,

View File

@ -1,5 +1,6 @@
const builtin = @import("builtin");
const std = @import("../std.zig");
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
const iovec = std.os.iovec;
@ -476,11 +477,20 @@ pub const CLOCK = struct {
pub const sockaddr = extern struct {
len: u8,
family: u8,
family: sa_family_t,
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [126]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),

View File

@ -1,4 +1,5 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const builtin = @import("builtin");
const maxInt = std.math.maxInt;
const iovec = std.os.iovec;
@ -401,7 +402,16 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [126]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),

View File

@ -1,4 +1,5 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const builtin = @import("builtin");
const maxInt = std.math.maxInt;
const iovec = std.os.iovec;
@ -339,7 +340,16 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [126]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),

View File

@ -1,4 +1,5 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const builtin = @import("builtin");
const maxInt = std.math.maxInt;
const iovec = std.os.iovec;
@ -481,7 +482,16 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [126]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),

View File

@ -1,4 +1,5 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
const builtin = @import("builtin");
const iovec = std.os.iovec;
@ -372,7 +373,16 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 256;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
len: u8 align(8),
family: sa_family_t,
padding: [254]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
len: u8 = @sizeOf(in),

View File

@ -1,4 +1,5 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const builtin = @import("builtin");
const maxInt = std.math.maxInt;
const iovec = std.os.iovec;
@ -435,7 +436,15 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 256;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
family: sa_family_t align(8),
padding: [254]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
pub const in = extern struct {
family: sa_family_t = AF.INET,

View File

@ -5616,11 +5616,11 @@ pub fn sendmsg(
/// The file descriptor of the sending socket.
sockfd: socket_t,
/// Message header and iovecs
msg: msghdr_const,
msg: *const msghdr_const,
flags: u32,
) SendMsgError!usize {
while (true) {
const rc = system.sendmsg(sockfd, @ptrCast(*const std.x.os.Socket.Message, &msg), @intCast(c_int, flags));
const rc = system.sendmsg(sockfd, msg, flags);
if (builtin.os.tag == .windows) {
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {

View File

@ -1226,11 +1226,14 @@ pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noal
return syscall5(.getsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen));
}
pub fn sendmsg(fd: i32, msg: *const std.x.os.Socket.Message, flags: c_int) usize {
pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize {
const fd_usize = @bitCast(usize, @as(isize, fd));
const msg_usize = @ptrToInt(msg);
if (native_arch == .x86) {
return socketcall(SC.sendmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) });
return socketcall(SC.sendmsg, &[3]usize{ fd_usize, msg_usize, flags });
} else {
return syscall3(.sendmsg, fd_usize, msg_usize, flags);
}
return syscall3(.sendmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)));
}
pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize {
@ -1274,24 +1277,42 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize
}
pub fn connect(fd: i32, addr: *const anyopaque, len: socklen_t) usize {
const fd_usize = @bitCast(usize, @as(isize, fd));
const addr_usize = @ptrToInt(addr);
if (native_arch == .x86) {
return socketcall(SC.connect, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len });
return socketcall(SC.connect, &[3]usize{ fd_usize, addr_usize, len });
} else {
return syscall3(.connect, fd_usize, addr_usize, len);
}
return syscall3(.connect, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len);
}
pub fn recvmsg(fd: i32, msg: *std.x.os.Socket.Message, flags: c_int) usize {
pub fn recvmsg(fd: i32, msg: *msghdr, flags: u32) usize {
const fd_usize = @bitCast(usize, @as(isize, fd));
const msg_usize = @ptrToInt(msg);
if (native_arch == .x86) {
return socketcall(SC.recvmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) });
return socketcall(SC.recvmsg, &[3]usize{ fd_usize, msg_usize, flags });
} else {
return syscall3(.recvmsg, fd_usize, msg_usize, flags);
}
return syscall3(.recvmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)));
}
pub fn recvfrom(fd: i32, noalias buf: [*]u8, len: usize, flags: u32, noalias addr: ?*sockaddr, noalias alen: ?*socklen_t) usize {
pub fn recvfrom(
fd: i32,
noalias buf: [*]u8,
len: usize,
flags: u32,
noalias addr: ?*sockaddr,
noalias alen: ?*socklen_t,
) usize {
const fd_usize = @bitCast(usize, @as(isize, fd));
const buf_usize = @ptrToInt(buf);
const addr_usize = @ptrToInt(addr);
const alen_usize = @ptrToInt(alen);
if (native_arch == .x86) {
return socketcall(SC.recvfrom, &[6]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen) });
return socketcall(SC.recvfrom, &[6]usize{ fd_usize, buf_usize, len, flags, addr_usize, alen_usize });
} else {
return syscall6(.recvfrom, fd_usize, buf_usize, len, flags, addr_usize, alen_usize);
}
return syscall6(.recvfrom, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen));
}
pub fn shutdown(fd: i32, how: i32) usize {
@ -3219,7 +3240,15 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
family: sa_family_t align(8),
padding: [SS_MAXSIZE - @sizeOf(sa_family_t)]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
/// IPv4 socket address
pub const in = extern struct {

View File

@ -6,16 +6,14 @@
//! isn't that useful for general-purpose applications, and so a mode that
//! utilizes user-supplied filters mode was added.
//!
//! Seccomp filters are classic BPF programs, which means that all the
//! information under `std.x.net.bpf` applies here as well. Conceptually, a
//! seccomp program is attached to the kernel and is executed on each syscall.
//! The "packet" being validated is the `data` structure, and the verdict is an
//! action that the kernel performs on the calling process. The actions are
//! variations on a "pass" or "fail" result, where a pass allows the syscall to
//! continue and a fail blocks the syscall and returns some sort of error value.
//! See the full list of actions under ::RET for more information. Finally, only
//! word-sized, absolute loads (`ld [k]`) are supported to read from the `data`
//! structure.
//! Seccomp filters are classic BPF programs. Conceptually, a seccomp program
//! is attached to the kernel and is executed on each syscall. The "packet"
//! being validated is the `data` structure, and the verdict is an action that
//! the kernel performs on the calling process. The actions are variations on a
//! "pass" or "fail" result, where a pass allows the syscall to continue and a
//! fail blocks the syscall and returns some sort of error value. See the full
//! list of actions under ::RET for more information. Finally, only word-sized,
//! absolute loads (`ld [k]`) are supported to read from the `data` structure.
//!
//! There are some issues with the filter API that have traditionally made
//! writing them a pain:

View File

@ -1,4 +1,5 @@
const std = @import("../../std.zig");
const assert = std.debug.assert;
const windows = std.os.windows;
const WINAPI = windows.WINAPI;
@ -1106,7 +1107,15 @@ pub const sockaddr = extern struct {
data: [14]u8,
pub const SS_MAXSIZE = 128;
pub const storage = std.x.os.Socket.Address.Native.Storage;
pub const storage = extern struct {
family: ADDRESS_FAMILY align(8),
padding: [SS_MAXSIZE - @sizeOf(ADDRESS_FAMILY)]u8 = undefined,
comptime {
assert(@sizeOf(storage) == SS_MAXSIZE);
assert(@alignOf(storage) == 8);
}
};
/// IPv4 socket address
pub const in = extern struct {
@ -1207,7 +1216,7 @@ pub const LPFN_GETACCEPTEXSOCKADDRS = *const fn (
pub const LPFN_WSASENDMSG = *const fn (
s: SOCKET,
lpMsg: *const std.x.os.Socket.Message,
lpMsg: *const WSAMSG_const,
dwFlags: u32,
lpNumberOfBytesSent: ?*u32,
lpOverlapped: ?*OVERLAPPED,
@ -1216,7 +1225,7 @@ pub const LPFN_WSASENDMSG = *const fn (
pub const LPFN_WSARECVMSG = *const fn (
s: SOCKET,
lpMsg: *std.x.os.Socket.Message,
lpMsg: *WSAMSG,
lpdwNumberOfBytesRecv: ?*u32,
lpOverlapped: ?*OVERLAPPED,
lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
@ -2090,7 +2099,7 @@ pub extern "ws2_32" fn WSASend(
pub extern "ws2_32" fn WSASendMsg(
s: SOCKET,
lpMsg: *const std.x.os.Socket.Message,
lpMsg: *WSAMSG_const,
dwFlags: u32,
lpNumberOfBytesSent: ?*u32,
lpOverlapped: ?*OVERLAPPED,
@ -2099,7 +2108,7 @@ pub extern "ws2_32" fn WSASendMsg(
pub extern "ws2_32" fn WSARecvMsg(
s: SOCKET,
lpMsg: *std.x.os.Socket.Message,
lpMsg: *WSAMSG,
lpdwNumberOfBytesRecv: ?*u32,
lpOverlapped: ?*OVERLAPPED,
lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,

View File

@ -90,7 +90,6 @@ pub const tz = @import("tz.zig");
pub const unicode = @import("unicode.zig");
pub const valgrind = @import("valgrind.zig");
pub const wasm = @import("wasm.zig");
pub const x = @import("x.zig");
pub const zig = @import("zig.zig");
pub const start = @import("start.zig");

View File

@ -1,19 +0,0 @@
const std = @import("std.zig");
pub const os = struct {
pub const Socket = @import("x/os/socket.zig").Socket;
pub usingnamespace @import("x/os/io.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");
pub const bpf = @import("x/net/bpf.zig");
};
test {
inline for (.{ os, net }) |module| {
std.testing.refAllDecls(module);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
const std = @import("../../std.zig");
const fmt = std.fmt;
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 {
if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
_ = opts;
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};

View File

@ -1,447 +0,0 @@
const std = @import("../../std.zig");
const builtin = @import("builtin");
const io = std.io;
const os = std.os;
const ip = std.x.net.ip;
const fmt = std.fmt;
const mem = std.mem;
const testing = std.testing;
const native_os = builtin.os;
const IPv4 = std.x.os.IPv4;
const IPv6 = std.x.os.IPv6;
const Socket = std.x.os.Socket;
const Buffer = std.x.os.Buffer;
/// 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 = enum(u16) {
ip = os.AF.INET,
ipv6 = os.AF.INET6,
};
/// A TCP client.
pub const Client = struct {
socket: Socket,
/// Implements `std.io.Reader`.
pub const Reader = struct {
client: Client,
flags: u32,
/// Implements `readFn` for `std.io.Reader`.
pub fn read(self: Client.Reader, buffer: []u8) !usize {
return self.client.read(buffer, self.flags);
}
};
/// Implements `std.io.Writer`.
pub const Writer = struct {
client: Client,
flags: u32,
/// Implements `writeFn` for `std.io.Writer`.
pub fn write(self: Client.Writer, buffer: []const u8) !usize {
return self.client.write(buffer, self.flags);
}
};
/// Opens a new client.
pub fn init(domain: tcp.Domain, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Client {
return Client{
.socket = try Socket.init(
@enumToInt(domain),
os.SOCK.STREAM,
os.IPPROTO.TCP,
flags,
),
};
}
/// 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());
}
/// Extracts the error set of a function.
/// TODO: remove after Socket.{read, write} error unions are well-defined across different platforms
fn ErrorSetOf(comptime Function: anytype) type {
return @typeInfo(@typeInfo(@TypeOf(Function)).Fn.return_type.?).ErrorUnion.error_set;
}
/// Wrap `tcp.Client` into `std.io.Reader`.
pub fn reader(self: Client, flags: u32) io.Reader(Client.Reader, ErrorSetOf(Client.Reader.read), Client.Reader.read) {
return .{ .context = .{ .client = self, .flags = flags } };
}
/// Wrap `tcp.Client` into `std.io.Writer`.
pub fn writer(self: Client, flags: u32) io.Writer(Client.Writer, ErrorSetOf(Client.Writer.write), Client.Writer.write) {
return .{ .context = .{ .client = self, .flags = flags } };
}
/// 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 read(self: Client, buf: []u8, flags: u32) !usize {
return self.socket.read(buf, flags);
}
/// 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 write(self: Client, buf: []const u8, flags: u32) !usize {
return self.socket.write(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 writeMessage(self: Client, msg: Socket.Message, flags: u32) !usize {
return self.socket.writeMessage(msg, flags);
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readMessage(self: Client, msg: *Socket.Message, flags: u32) !usize {
return self.socket.readMessage(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());
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Client) !ip.Address {
return ip.Address.from(try self.socket.getRemoteAddress());
}
/// Have close() or shutdown() syscalls block until all queued messages in the client have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Client, timeout_seconds: ?u16) !void {
return self.socket.setLinger(timeout_seconds);
}
/// Have keep-alive messages be sent periodically. The timing in which keep-alive messages are sent are
/// dependant on operating system settings. It returns `error.UnsupportedSocketOption` if the host does
/// not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Client, enabled: bool) !void {
return self.socket.setKeepAlive(enabled);
}
/// 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 (@hasDecl(os.TCP, "NODELAY")) {
const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
return self.socket.setOption(os.IPPROTO.TCP, os.TCP.NODELAY, bytes);
}
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: Client, enabled: bool) !void {
if (@hasDecl(os.TCP, "QUICKACK")) {
return self.socket.setOption(os.IPPROTO.TCP, os.TCP.QUICKACK, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
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: u32) !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: u32) !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: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Listener {
return Listener{
.socket = try Socket.init(
@enumToInt(domain),
os.SOCK.STREAM,
os.IPPROTO.TCP,
flags,
),
};
}
/// 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: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !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 (@hasDecl(os.TCP, "FASTOPEN")) {
return self.socket.setOption(os.IPPROTO.TCP, os.TCP.FASTOPEN, mem.asBytes(&@as(u32, @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 (native_os.tag == .wasi) return error.SkipZigTest;
const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
var binded_address = try listener.getLocalAddress();
switch (binded_address) {
.ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
.ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
}
const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
defer client.deinit();
try client.connect(binded_address);
const conn = try listener.accept(.{ .close_on_exec = true });
defer conn.deinit();
}
test "tcp/client: 1ms read timeout" {
if (native_os.tag == .wasi) return error.SkipZigTest;
const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
var binded_address = try listener.getLocalAddress();
switch (binded_address) {
.ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
.ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
}
const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
defer client.deinit();
try client.connect(binded_address);
try client.setReadTimeout(1);
const conn = try listener.accept(.{ .close_on_exec = true });
defer conn.deinit();
var buf: [1]u8 = undefined;
try testing.expectError(error.WouldBlock, client.reader(0).read(&buf));
}
test "tcp/client: read and write multiple vectors" {
if (native_os.tag == .wasi) return error.SkipZigTest;
if (builtin.os.tag == .windows) {
// https://github.com/ziglang/zig/issues/13893
return error.SkipZigTest;
}
const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
var binded_address = try listener.getLocalAddress();
switch (binded_address) {
.ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
.ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
}
const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
defer client.deinit();
try client.connect(binded_address);
const conn = try listener.accept(.{ .close_on_exec = true });
defer conn.deinit();
const message = "hello world";
_ = try conn.client.writeMessage(Socket.Message.fromBuffers(&[_]Buffer{
Buffer.from(message[0 .. message.len / 2]),
Buffer.from(message[message.len / 2 ..]),
}), 0);
var buf: [message.len + 1]u8 = undefined;
var msg = Socket.Message.fromBuffers(&[_]Buffer{
Buffer.from(buf[0 .. message.len / 2]),
Buffer.from(buf[message.len / 2 ..]),
});
_ = try client.readMessage(&msg, 0);
try testing.expectEqualStrings(message, buf[0..message.len]);
}
test "tcp/listener: bind to unspecified ipv4 address" {
if (native_os.tag == .wasi) return error.SkipZigTest;
const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
const address = try listener.getLocalAddress();
try testing.expect(address == .ipv4);
}
test "tcp/listener: bind to unspecified ipv6 address" {
if (native_os.tag == .wasi) return error.SkipZigTest;
if (builtin.os.tag == .windows) {
// https://github.com/ziglang/zig/issues/13893
return error.SkipZigTest;
}
const listener = try tcp.Listener.init(.ipv6, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0));
try listener.listen(128);
const address = try listener.getLocalAddress();
try testing.expect(address == .ipv6);
}

View File

@ -1,224 +0,0 @@
const std = @import("../../std.zig");
const builtin = @import("builtin");
const os = std.os;
const mem = std.mem;
const testing = std.testing;
const native_os = builtin.os;
const linux = std.os.linux;
/// POSIX `iovec`, or Windows `WSABUF`. The difference between the two are the ordering
/// of fields, alongside the length being represented as either a ULONG or a size_t.
pub const Buffer = if (native_os.tag == .windows)
extern struct {
len: c_ulong,
ptr: usize,
pub fn from(slice: []const u8) Buffer {
return .{ .len = @intCast(c_ulong, slice.len), .ptr = @ptrToInt(slice.ptr) };
}
pub fn into(self: Buffer) []const u8 {
return @intToPtr([*]const u8, self.ptr)[0..self.len];
}
pub fn intoMutable(self: Buffer) []u8 {
return @intToPtr([*]u8, self.ptr)[0..self.len];
}
}
else
extern struct {
ptr: usize,
len: usize,
pub fn from(slice: []const u8) Buffer {
return .{ .ptr = @ptrToInt(slice.ptr), .len = slice.len };
}
pub fn into(self: Buffer) []const u8 {
return @intToPtr([*]const u8, self.ptr)[0..self.len];
}
pub fn intoMutable(self: Buffer) []u8 {
return @intToPtr([*]u8, self.ptr)[0..self.len];
}
};
pub const Reactor = struct {
pub const InitFlags = enum {
close_on_exec,
};
pub const Event = struct {
data: usize,
is_error: bool,
is_hup: bool,
is_readable: bool,
is_writable: bool,
};
pub const Interest = struct {
hup: bool = false,
oneshot: bool = false,
readable: bool = false,
writable: bool = false,
};
fd: os.fd_t,
pub fn init(flags: std.enums.EnumFieldStruct(Reactor.InitFlags, bool, false)) !Reactor {
var raw_flags: u32 = 0;
const set = std.EnumSet(Reactor.InitFlags).init(flags);
if (set.contains(.close_on_exec)) raw_flags |= linux.EPOLL.CLOEXEC;
return Reactor{ .fd = try os.epoll_create1(raw_flags) };
}
pub fn deinit(self: Reactor) void {
os.close(self.fd);
}
pub fn update(self: Reactor, fd: os.fd_t, identifier: usize, interest: Reactor.Interest) !void {
var flags: u32 = 0;
flags |= if (interest.oneshot) linux.EPOLL.ONESHOT else linux.EPOLL.ET;
if (interest.hup) flags |= linux.EPOLL.RDHUP;
if (interest.readable) flags |= linux.EPOLL.IN;
if (interest.writable) flags |= linux.EPOLL.OUT;
const event = &linux.epoll_event{
.events = flags,
.data = .{ .ptr = identifier },
};
os.epoll_ctl(self.fd, linux.EPOLL.CTL_MOD, fd, event) catch |err| switch (err) {
error.FileDescriptorNotRegistered => try os.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, fd, event),
else => return err,
};
}
pub fn remove(self: Reactor, fd: os.fd_t) !void {
// directly from man epoll_ctl BUGS section
// In kernel versions before 2.6.9, the EPOLL_CTL_DEL operation re
// quired a non-null pointer in event, even though this argument is
// ignored. Since Linux 2.6.9, event can be specified as NULL when
// using EPOLL_CTL_DEL. Applications that need to be portable to
// kernels before 2.6.9 should specify a non-null pointer in event.
var event = linux.epoll_event{
.events = 0,
.data = .{ .ptr = 0 },
};
return os.epoll_ctl(self.fd, linux.EPOLL.CTL_DEL, fd, &event);
}
pub fn poll(self: Reactor, comptime max_num_events: comptime_int, closure: anytype, timeout_milliseconds: ?u64) !void {
var events: [max_num_events]linux.epoll_event = undefined;
const num_events = os.epoll_wait(self.fd, &events, if (timeout_milliseconds) |ms| @intCast(i32, ms) else -1);
for (events[0..num_events]) |ev| {
const is_error = ev.events & linux.EPOLL.ERR != 0;
const is_hup = ev.events & (linux.EPOLL.HUP | linux.EPOLL.RDHUP) != 0;
const is_readable = ev.events & linux.EPOLL.IN != 0;
const is_writable = ev.events & linux.EPOLL.OUT != 0;
try closure.call(Reactor.Event{
.data = ev.data.ptr,
.is_error = is_error,
.is_hup = is_hup,
.is_readable = is_readable,
.is_writable = is_writable,
});
}
}
};
test "reactor/linux: drive async tcp client/listener pair" {
if (native_os.tag != .linux) return error.SkipZigTest;
const ip = std.x.net.ip;
const tcp = std.x.net.tcp;
const IPv4 = std.x.os.IPv4;
const IPv6 = std.x.os.IPv6;
const reactor = try Reactor.init(.{ .close_on_exec = true });
defer reactor.deinit();
const listener = try tcp.Listener.init(.ip, .{
.close_on_exec = true,
.nonblocking = true,
});
defer listener.deinit();
try reactor.update(listener.socket.fd, 0, .{ .readable = true });
try reactor.poll(1, struct {
fn call(event: Reactor.Event) !void {
try testing.expectEqual(Reactor.Event{
.data = 0,
.is_error = false,
.is_hup = true,
.is_readable = false,
.is_writable = false,
}, event);
}
}, null);
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
var binded_address = try listener.getLocalAddress();
switch (binded_address) {
.ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
.ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
}
const client = try tcp.Client.init(.ip, .{
.close_on_exec = true,
.nonblocking = true,
});
defer client.deinit();
try reactor.update(client.socket.fd, 1, .{ .readable = true, .writable = true });
try reactor.poll(1, struct {
fn call(event: Reactor.Event) !void {
try testing.expectEqual(Reactor.Event{
.data = 1,
.is_error = false,
.is_hup = true,
.is_readable = false,
.is_writable = true,
}, event);
}
}, null);
client.connect(binded_address) catch |err| switch (err) {
error.WouldBlock => {},
else => return err,
};
try reactor.poll(1, struct {
fn call(event: Reactor.Event) !void {
try testing.expectEqual(Reactor.Event{
.data = 1,
.is_error = false,
.is_hup = false,
.is_readable = false,
.is_writable = true,
}, event);
}
}, null);
try reactor.poll(1, struct {
fn call(event: Reactor.Event) !void {
try testing.expectEqual(Reactor.Event{
.data = 0,
.is_error = false,
.is_hup = false,
.is_readable = true,
.is_writable = false,
}, event);
}
}, null);
try reactor.remove(client.socket.fd);
try reactor.remove(listener.socket.fd);
}

View File

@ -1,605 +0,0 @@
const std = @import("../../std.zig");
const builtin = @import("builtin");
const os = std.os;
const fmt = std.fmt;
const mem = std.mem;
const math = std.math;
const testing = std.testing;
const native_os = builtin.os;
const have_ifnamesize = @hasDecl(os.system, "IFNAMESIZE");
pub const ResolveScopeIdError = error{
NameTooLong,
PermissionDenied,
AddressFamilyNotSupported,
ProtocolFamilyNotAvailable,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
SystemResources,
ProtocolNotSupported,
SocketTypeNotSupported,
InterfaceNotFound,
FileSystem,
Unexpected,
};
/// 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) ResolveScopeIdError!u32 {
if (have_ifnamesize) {
if (name.len >= os.IFNAMESIZE) return error.NameTooLong;
if (native_os.tag == .windows or comptime native_os.tag.isDarwin()) {
var interface_name: [os.IFNAMESIZE:0]u8 = undefined;
mem.copy(u8, &interface_name, name);
interface_name[name.len] = 0;
const rc = blk: {
if (native_os.tag == .windows) {
break :blk os.windows.ws2_32.if_nametoindex(@ptrCast([*:0]const u8, &interface_name));
} else {
const index = os.system.if_nametoindex(@ptrCast([*:0]const u8, &interface_name));
break :blk @bitCast(u32, index);
}
};
if (rc == 0) {
return error.InterfaceNotFound;
}
return rc;
}
if (native_os.tag == .linux) {
const fd = try os.socket(os.AF.INET, 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.InterfaceNotFound;
}
/// 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 {
_ = opts;
if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
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 {
_ = opts;
const specifier = comptime &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) {
'x', 'X' => |specifier| specifier,
's' => 'x',
'S' => 'X',
else => std.fmt.invalidFmtError(layout, self),
}};
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: struct { from: usize, to: usize } = 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,
InterfaceNotFound,
UnknownScopeId,
} || 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.UnknownScopeId;
const scope_id: u32 = switch (scope_id_slice[0]) {
'0'...'9' => fmt.parseInt(u32, scope_id_slice, 10),
else => resolveScopeId(scope_id_slice) catch |err| switch (err) {
error.InterfaceNotFound => return error.InterfaceNotFound,
else => err,
},
} catch return error.UnknownScopeId;
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()});
try testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4());
try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()});
try testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4());
try 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 (!have_ifnamesize) return error.SkipZigTest;
const iface = if (native_os.tag == .linux)
"lo"
else
"lo0";
const input = "FF01::FB%" ++ iface;
const output = "ff01::fb%1";
const parsed = IPv6.parse(input) catch |err| switch (err) {
error.InterfaceNotFound => return,
else => return err,
};
try testing.expectFmt(output, "{}", .{parsed});
}

View File

@ -1,320 +0,0 @@
const std = @import("../../std.zig");
const builtin = @import("builtin");
const net = @import("net.zig");
const os = std.os;
const fmt = std.fmt;
const mem = std.mem;
const time = std.time;
const meta = std.meta;
const native_os = builtin.os;
const native_endian = builtin.cpu.arch.endian();
const Buffer = std.x.os.Buffer;
const assert = std.debug.assert;
/// A generic, cross-platform socket abstraction.
pub const Socket = struct {
/// 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) {
pub const Native = struct {
pub const requires_prepended_length = native_os.getVersionRange() == .semver;
pub const Length = if (requires_prepended_length) u8 else [0]u8;
pub const Family = if (requires_prepended_length) u8 else c_ushort;
/// POSIX `sockaddr.storage`. The expected size and alignment is specified in IETF RFC 2553.
pub const Storage = extern struct {
pub const expected_size = os.sockaddr.SS_MAXSIZE;
pub const expected_alignment = 8;
pub const padding_size = expected_size -
mem.alignForward(@sizeOf(Address.Native.Length), expected_alignment) -
mem.alignForward(@sizeOf(Address.Native.Family), expected_alignment);
len: Address.Native.Length align(expected_alignment) = undefined,
family: Address.Native.Family align(expected_alignment) = undefined,
padding: [padding_size]u8 align(expected_alignment) = undefined,
comptime {
assert(@sizeOf(Storage) == Storage.expected_size);
assert(@alignOf(Storage) == Storage.expected_alignment);
}
};
};
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 {
if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
_ = opts;
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// POSIX `msghdr`. Denotes a destination address, set of buffers, control data, and flags. Ported
/// directly from musl.
pub const Message = if (native_os.isAtLeast(.windows, .vista) != null and native_os.isAtLeast(.windows, .vista).?)
extern struct {
name: usize = @ptrToInt(@as(?[*]u8, null)),
name_len: c_int = 0,
buffers: usize = undefined,
buffers_len: c_ulong = undefined,
control: Buffer = .{
.ptr = @ptrToInt(@as(?[*]u8, null)),
.len = 0,
},
flags: c_ulong = 0,
pub usingnamespace MessageMixin(Message);
}
else if (native_os.tag == .windows)
extern struct {
name: usize = @ptrToInt(@as(?[*]u8, null)),
name_len: c_int = 0,
buffers: usize = undefined,
buffers_len: u32 = undefined,
control: Buffer = .{
.ptr = @ptrToInt(@as(?[*]u8, null)),
.len = 0,
},
flags: u32 = 0,
pub usingnamespace MessageMixin(Message);
}
else if (@sizeOf(usize) > 4 and native_endian == .Big)
extern struct {
name: usize = @ptrToInt(@as(?[*]u8, null)),
name_len: c_uint = 0,
buffers: usize = undefined,
_pad_1: c_int = 0,
buffers_len: c_int = undefined,
control: usize = @ptrToInt(@as(?[*]u8, null)),
_pad_2: c_int = 0,
control_len: c_uint = 0,
flags: c_int = 0,
pub usingnamespace MessageMixin(Message);
}
else if (@sizeOf(usize) > 4 and native_endian == .Little)
extern struct {
name: usize = @ptrToInt(@as(?[*]u8, null)),
name_len: c_uint = 0,
buffers: usize = undefined,
buffers_len: c_int = undefined,
_pad_1: c_int = 0,
control: usize = @ptrToInt(@as(?[*]u8, null)),
control_len: c_uint = 0,
_pad_2: c_int = 0,
flags: c_int = 0,
pub usingnamespace MessageMixin(Message);
}
else
extern struct {
name: usize = @ptrToInt(@as(?[*]u8, null)),
name_len: c_uint = 0,
buffers: usize = undefined,
buffers_len: c_int = undefined,
control: usize = @ptrToInt(@as(?[*]u8, null)),
control_len: c_uint = 0,
flags: c_int = 0,
pub usingnamespace MessageMixin(Message);
};
fn MessageMixin(comptime Self: type) type {
return struct {
pub fn fromBuffers(buffers: []const Buffer) Self {
var self: Self = .{};
self.setBuffers(buffers);
return self;
}
pub fn setName(self: *Self, name: []const u8) void {
self.name = @ptrToInt(name.ptr);
self.name_len = @intCast(meta.fieldInfo(Self, .name_len).type, name.len);
}
pub fn setBuffers(self: *Self, buffers: []const Buffer) void {
self.buffers = @ptrToInt(buffers.ptr);
self.buffers_len = @intCast(meta.fieldInfo(Self, .buffers_len).type, buffers.len);
}
pub fn setControl(self: *Self, control: []const u8) void {
if (native_os.tag == .windows) {
self.control = Buffer.from(control);
} else {
self.control = @ptrToInt(control.ptr);
self.control_len = @intCast(meta.fieldInfo(Self, .control_len).type, control.len);
}
}
pub fn setFlags(self: *Self, flags: u32) void {
self.flags = @intCast(meta.fieldInfo(Self, .flags).type, flags);
}
pub fn getName(self: Self) []const u8 {
return @intToPtr([*]const u8, self.name)[0..@intCast(usize, self.name_len)];
}
pub fn getBuffers(self: Self) []const Buffer {
return @intToPtr([*]const Buffer, self.buffers)[0..@intCast(usize, self.buffers_len)];
}
pub fn getControl(self: Self) []const u8 {
if (native_os.tag == .windows) {
return self.control.into();
} else {
return @intToPtr([*]const u8, self.control)[0..@intCast(usize, self.control_len)];
}
}
pub fn getFlags(self: Self) u32 {
return @intCast(u32, self.flags);
}
};
}
/// POSIX `linger`, denoting the linger settings of a socket.
///
/// Microsoft's documentation and glibc denote the fields to be unsigned
/// short's on Windows, whereas glibc and musl denote the fields to be
/// int's on every other platform.
pub const Linger = extern struct {
pub const Field = switch (native_os.tag) {
.windows => c_ushort,
else => c_int,
};
enabled: Field,
timeout_seconds: Field,
pub fn init(timeout_seconds: ?u16) Socket.Linger {
return .{
.enabled = @intCast(Socket.Linger.Field, @boolToInt(timeout_seconds != null)),
.timeout_seconds = if (timeout_seconds) |seconds| @intCast(Socket.Linger.Field, seconds) else 0,
};
}
};
/// Possible set of flags to initialize a socket with.
pub const InitFlags = enum {
// Initialize a socket to be non-blocking.
nonblocking,
// Have a socket close itself on exec syscalls.
close_on_exec,
};
/// The underlying handle of a socket.
fd: os.socket_t,
/// Enclose a socket abstraction over an existing socket file descriptor.
pub fn from(fd: os.socket_t) Socket {
return Socket{ .fd = fd };
}
/// Mix in socket syscalls depending on the platform we are compiling against.
pub usingnamespace switch (native_os.tag) {
.windows => @import("socket_windows.zig"),
else => @import("socket_posix.zig"),
}.Mixin(Socket);
};

View File

@ -1,275 +0,0 @@
const std = @import("../../std.zig");
const os = std.os;
const mem = std.mem;
const time = std.time;
pub fn Mixin(comptime Socket: type) type {
return struct {
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket {
var raw_flags: u32 = socket_type;
const set = std.EnumSet(Socket.InitFlags).init(flags);
if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC;
if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK;
return Socket{ .fd = try os.socket(domain, raw_flags, protocol) };
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
os.closeSocket(self.fd);
}
/// 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: Socket.Address) !void {
return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
}
/// Start listening for incoming connections on the socket.
pub fn listen(self: Socket, max_backlog_size: u31) !void {
return os.listen(self.fd, max_backlog_size);
}
/// Have the socket attempt to the connect to an 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, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection {
var address: Socket.Address.Native.Storage = undefined;
var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
var raw_flags: u32 = 0;
const set = std.EnumSet(Socket.InitFlags).init(flags);
if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC;
if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK;
const socket = Socket{ .fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, raw_flags) };
const socket_address = Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
return Socket.Connection.from(socket, socket_address);
}
/// 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 read(self: Socket, buf: []u8, flags: u32) !usize {
return os.recv(self.fd, buf, flags);
}
/// 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 write(self: Socket, buf: []const u8, flags: u32) !usize {
return os.send(self.fd, 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 writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize {
while (true) {
const rc = os.system.sendmsg(self.fd, &msg, @intCast(c_int, flags));
return switch (os.errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.ACCES => error.AccessDenied,
.AGAIN => error.WouldBlock,
.ALREADY => error.FastOpenAlreadyInProgress,
.BADF => unreachable, // always a race condition
.CONNRESET => error.ConnectionResetByPeer,
.DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
.FAULT => unreachable, // An invalid user space address was specified for an argument.
.INTR => continue,
.INVAL => unreachable, // Invalid argument passed.
.ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
.MSGSIZE => error.MessageTooBig,
.NOBUFS => error.SystemResources,
.NOMEM => error.SystemResources,
.NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
.OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
.PIPE => error.BrokenPipe,
.AFNOSUPPORT => error.AddressFamilyNotSupported,
.LOOP => error.SymLinkLoop,
.NAMETOOLONG => error.NameTooLong,
.NOENT => error.FileNotFound,
.NOTDIR => error.NotDir,
.HOSTUNREACH => error.NetworkUnreachable,
.NETUNREACH => error.NetworkUnreachable,
.NOTCONN => error.SocketNotConnected,
.NETDOWN => error.NetworkSubsystemFailed,
else => |err| os.unexpectedErrno(err),
};
}
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize {
while (true) {
const rc = os.system.recvmsg(self.fd, msg, @intCast(c_int, flags));
return switch (os.errno(rc)) {
.SUCCESS => @intCast(usize, rc),
.BADF => unreachable, // always a race condition
.FAULT => unreachable,
.INVAL => unreachable,
.NOTCONN => unreachable,
.NOTSOCK => unreachable,
.INTR => continue,
.AGAIN => error.WouldBlock,
.NOMEM => error.SystemResources,
.CONNREFUSED => error.ConnectionRefused,
.CONNRESET => error.ConnectionResetByPeer,
else => |err| os.unexpectedErrno(err),
};
}
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: Socket.Address.Native.Storage = undefined;
var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: Socket.Address.Native.Storage = undefined;
var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return os.getsockoptError(self.fd);
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
.SUCCESS => value,
.BADF => error.BadFileDescriptor,
.FAULT => error.InvalidAddressSpace,
.INVAL => error.InvalidSocketOption,
.NOPROTOOPT => error.UnknownSocketOption,
.NOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
.SUCCESS => value,
.BADF => error.BadFileDescriptor,
.FAULT => error.InvalidAddressSpace,
.INVAL => error.InvalidSocketOption,
.NOPROTOOPT => error.UnknownSocketOption,
.NOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Set a socket option.
pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void {
return os.setsockopt(self.fd, level, code, value);
}
/// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
if (@hasDecl(os.SO, "LINGER")) {
const settings = Socket.Linger.init(timeout_seconds);
return self.setOption(os.SOL.SOCKET, os.SO.LINGER, mem.asBytes(&settings));
}
return error.UnsupportedSocketOption;
}
/// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
/// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
/// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Socket, enabled: bool) !void {
if (@hasDecl(os.SO, "KEEPALIVE")) {
return self.setOption(os.SOL.SOCKET, os.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// 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: Socket, enabled: bool) !void {
if (@hasDecl(os.SO, "REUSEADDR")) {
return self.setOption(os.SOL.SOCKET, os.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// 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: Socket, enabled: bool) !void {
if (@hasDecl(os.SO, "REUSEPORT")) {
return self.setOption(os.SOL.SOCKET, os.SO.REUSEPORT, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
return error.UnsupportedSocketOption;
}
/// Set the write buffer size of the socket.
pub fn setWriteBufferSize(self: Socket, size: u32) !void {
return self.setOption(os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&size));
}
/// Set the read buffer size of the socket.
pub fn setReadBufferSize(self: Socket, size: u32) !void {
return self.setOption(os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&size));
}
/// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
/// set on a non-blocking socket.
///
/// 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: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return self.setOption(os.SOL.SOCKET, os.SO.SNDTIMEO, mem.asBytes(&timeout));
}
/// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
/// set on a non-blocking socket.
///
/// 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: Socket, milliseconds: usize) !void {
const timeout = os.timeval{
.tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
.tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
};
return self.setOption(os.SOL.SOCKET, os.SO.RCVTIMEO, mem.asBytes(&timeout));
}
};
}

View File

@ -1,458 +0,0 @@
const std = @import("../../std.zig");
const net = @import("net.zig");
const os = std.os;
const mem = std.mem;
const windows = std.os.windows;
const ws2_32 = windows.ws2_32;
pub fn Mixin(comptime Socket: type) type {
return struct {
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket {
var raw_flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED;
const set = std.EnumSet(Socket.InitFlags).init(flags);
if (set.contains(.close_on_exec)) raw_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
const fd = ws2_32.WSASocketW(
@intCast(i32, domain),
@intCast(i32, socket_type),
@intCast(i32, protocol),
null,
0,
raw_flags,
);
if (fd == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => {
_ = try windows.WSAStartup(2, 2);
return init(domain, socket_type, protocol, flags);
},
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENOBUFS => error.SystemResources,
.WSAEPROTONOSUPPORT => error.ProtocolNotSupported,
else => |err| windows.unexpectedWSAError(err),
};
}
if (set.contains(.nonblocking)) {
var enabled: c_ulong = 1;
const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled);
if (rc == ws2_32.SOCKET_ERROR) {
return windows.unexpectedWSAError(ws2_32.WSAGetLastError());
}
}
return Socket{ .fd = fd };
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
_ = ws2_32.closesocket(self.fd);
}
/// Shutdown either the read side, write side, or all side of the socket.
pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
const rc = ws2_32.shutdown(self.fd, switch (how) {
.recv => ws2_32.SD_RECEIVE,
.send => ws2_32.SD_SEND,
.both => ws2_32.SD_BOTH,
});
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => return error.ConnectionAborted,
.WSAECONNRESET => return error.ConnectionResetByPeer,
.WSAEINPROGRESS => return error.BlockingOperationInProgress,
.WSAEINVAL => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAENOTSOCK => unreachable,
.WSANOTINITIALISED => unreachable,
else => |err| return windows.unexpectedWSAError(err),
};
}
}
/// Binds the socket to an address.
pub fn bind(self: Socket, address: Socket.Address) !void {
const rc = ws2_32.bind(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAEACCES => error.AccessDenied,
.WSAEADDRINUSE => error.AddressInUse,
.WSAEADDRNOTAVAIL => error.AddressNotAvailable,
.WSAEFAULT => error.BadAddress,
.WSAEINPROGRESS => error.WouldBlock,
.WSAEINVAL => error.AlreadyBound,
.WSAENOBUFS => error.NoEphemeralPortsAvailable,
.WSAENOTSOCK => error.NotASocket,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Start listening for incoming connections on the socket.
pub fn listen(self: Socket, max_backlog_size: u31) !void {
const rc = ws2_32.listen(self.fd, max_backlog_size);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAEADDRINUSE => error.AddressInUse,
.WSAEISCONN => error.AlreadyConnected,
.WSAEINVAL => error.SocketNotBound,
.WSAEMFILE, .WSAENOBUFS => error.SystemResources,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAEINPROGRESS => error.WouldBlock,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Have the socket attempt to the connect to an address.
pub fn connect(self: Socket, address: Socket.Address) !void {
const rc = ws2_32.connect(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAEADDRINUSE => error.AddressInUse,
.WSAEADDRNOTAVAIL => error.AddressNotAvailable,
.WSAECONNREFUSED => error.ConnectionRefused,
.WSAETIMEDOUT => error.ConnectionTimedOut,
.WSAEFAULT => error.BadAddress,
.WSAEINVAL => error.ListeningSocket,
.WSAEISCONN => error.AlreadyConnected,
.WSAENOTSOCK => error.NotASocket,
.WSAEACCES => error.BroadcastNotEnabled,
.WSAENOBUFS => error.SystemResources,
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock,
.WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection {
var address: Socket.Address.Native.Storage = undefined;
var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
const fd = ws2_32.accept(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (fd == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEFAULT => unreachable,
.WSAEINVAL => error.SocketNotListening,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOBUFS => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAEWOULDBLOCK => error.WouldBlock,
else => |err| windows.unexpectedWSAError(err),
};
}
const socket = Socket.from(fd);
errdefer socket.deinit();
const socket_address = Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
const set = std.EnumSet(Socket.InitFlags).init(flags);
if (set.contains(.nonblocking)) {
var enabled: c_ulong = 1;
const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled);
if (rc == ws2_32.SOCKET_ERROR) {
return windows.unexpectedWSAError(ws2_32.WSAGetLastError());
}
}
return Socket.Connection.from(socket, socket_address);
}
/// 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 read(self: Socket, buf: []u8, flags: u32) !usize {
var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }};
var num_bytes: u32 = undefined;
var flags_ = flags;
const rc = ws2_32.WSARecv(self.fd, bufs, 1, &num_bytes, &flags_, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEDISCON => error.ConnectionClosedByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, num_bytes);
}
/// 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 write(self: Socket, buf: []const u8, flags: u32) !usize {
var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = @intToPtr([*]u8, @ptrToInt(buf.ptr)) }};
var num_bytes: u32 = undefined;
const rc = ws2_32.WSASend(self.fd, bufs, 1, &num_bytes, flags, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOBUFS => error.BufferDeadlock,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, num_bytes);
}
/// 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 writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize {
const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSASENDMSG, self.fd, ws2_32.WSAID_WSASENDMSG);
var num_bytes: u32 = undefined;
const rc = call(self.fd, &msg, flags, &num_bytes, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOBUFS => error.BufferDeadlock,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, num_bytes);
}
/// Read multiple I/O vectors with a prepended message header from the socket
/// with a set of flags specified. It returns the number of bytes that were
/// read into the buffer provided.
pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize {
_ = flags;
const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSARECVMSG, self.fd, ws2_32.WSAID_WSARECVMSG);
var num_bytes: u32 = undefined;
const rc = call(self.fd, msg, &num_bytes, null, null);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAECONNABORTED => error.ConnectionAborted,
.WSAECONNRESET => error.ConnectionResetByPeer,
.WSAEDISCON => error.ConnectionClosedByPeer,
.WSAEFAULT => error.BadBuffer,
.WSAEINPROGRESS,
.WSAEWOULDBLOCK,
.WSA_IO_PENDING,
.WSAETIMEDOUT,
=> error.WouldBlock,
.WSAEINTR => error.Cancelled,
.WSAEINVAL => error.SocketNotBound,
.WSAEMSGSIZE => error.MessageTooLarge,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENETRESET => error.NetworkReset,
.WSAENOTCONN => error.SocketNotConnected,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAESHUTDOWN => error.AlreadyShutdown,
.WSA_OPERATION_ABORTED => error.OperationAborted,
else => |err| windows.unexpectedWSAError(err),
};
}
return @intCast(usize, num_bytes);
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: Socket.Address.Native.Storage = undefined;
var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
const rc = ws2_32.getsockname(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAEFAULT => unreachable,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEINVAL => error.SocketNotBound,
else => |err| windows.unexpectedWSAError(err),
};
}
return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: Socket.Address.Native.Storage = undefined;
var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
const rc = ws2_32.getpeername(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAEFAULT => unreachable,
.WSAENETDOWN => error.NetworkSubsystemFailed,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
.WSAEINVAL => error.SocketNotBound,
else => |err| windows.unexpectedWSAError(err),
};
}
return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
_ = self;
return {};
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
_ = self;
return 0;
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
_ = self;
return 0;
}
/// Set a socket option.
pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void {
const rc = ws2_32.setsockopt(self.fd, @intCast(i32, level), @intCast(i32, code), value.ptr, @intCast(i32, value.len));
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAEFAULT => unreachable,
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
.WSAEINVAL => return error.SocketNotBound,
else => |err| windows.unexpectedWSAError(err),
};
}
}
/// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
/// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
/// if the host does not support the option for a socket to linger around up until a timeout specified in
/// seconds.
pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
const settings = Socket.Linger.init(timeout_seconds);
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.LINGER, mem.asBytes(&settings));
}
/// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
/// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
/// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
pub fn setKeepAlive(self: Socket, enabled: bool) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
}
/// 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: Socket, enabled: bool) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(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.
///
/// TODO: verify if this truly mimicks SO.REUSEPORT behavior, or if SO.REUSE_UNICASTPORT provides the correct behavior
pub fn setReusePort(self: Socket, enabled: bool) !void {
try self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.BROADCAST, mem.asBytes(&@as(u32, @boolToInt(enabled))));
try self.setReuseAddress(enabled);
}
/// Set the write buffer size of the socket.
pub fn setWriteBufferSize(self: Socket, size: u32) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDBUF, mem.asBytes(&size));
}
/// Set the read buffer size of the socket.
pub fn setReadBufferSize(self: Socket, size: u32) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVBUF, mem.asBytes(&size));
}
/// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
/// set on a non-blocking socket.
///
/// 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: Socket, milliseconds: u32) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDTIMEO, mem.asBytes(&milliseconds));
}
/// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
/// set on a non-blocking socket.
///
/// 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: Socket, milliseconds: u32) !void {
return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVTIMEO, mem.asBytes(&milliseconds));
}
};
}