x/os/socket, std/os/windows: implement loading winsock extensions

Implement loading Winsock extensions.

Add missing Winsock extension GUID's.

Implement readVectorized() for POSIX sockets and
readVectorized() / writeVectorized() for Windows
sockets.

Inverse how mixins are used to implement platform-independent syscalls
for the std.x.os.Socket abstraction. This cleans up the API as suggested
by @komuw.
This commit is contained in:
lithdew 2021-05-09 15:43:56 +09:00
parent 3d946ef5eb
commit 77f8a9ae22
5 changed files with 812 additions and 667 deletions

View File

@ -1749,6 +1749,38 @@ fn MAKELANGID(p: c_ushort, s: c_ushort) callconv(.Inline) LANGID {
return (s << 10) | p;
}
/// Loads a Winsock extension function in runtime specified by a GUID.
pub fn loadWinsockExtensionFunction(comptime T: type, sock: ws2_32.SOCKET, guid: GUID) !T {
var function: T = undefined;
var num_bytes: DWORD = undefined;
const rc = ws2_32.WSAIoctl(
sock,
ws2_32.SIO_GET_EXTENSION_FUNCTION_POINTER,
@ptrCast(*const c_void, &guid),
@sizeOf(GUID),
&function,
@sizeOf(T),
&num_bytes,
null,
null,
);
if (rc == ws2_32.SOCKET_ERROR) {
return switch (ws2_32.WSAGetLastError()) {
.WSAEOPNOTSUPP => error.OperationNotSupported,
.WSAENOTSOCK => error.FileDescriptorNotASocket,
else => |err| unexpectedWSAError(err),
};
}
if (num_bytes != @sizeOf(T)) {
return error.ShortRead;
}
return function;
}
/// Call this when you made a windows DLL call or something that does SetLastError
/// and you get an unexpected error.
pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError {

View File

@ -266,10 +266,54 @@ pub const SENDER_DEFAULT_LATE_JOINER_PERCENTAGE = 0;
pub const SENDER_MAX_LATE_JOINER_PERCENTAGE = 75;
pub const BITS_PER_BYTE = 8;
pub const LOG2_BITS_PER_BYTE = 3;
pub const SOCKET_DEFAULT2_QM_POLICY = GUID.parse("{aec2ef9c-3a4d-4d3e-8842-239942e39a47}");
pub const REAL_TIME_NOTIFICATION_CAPABILITY = GUID.parse("{6b59819a-5cae-492d-a901-2a3c2c50164f}");
pub const REAL_TIME_NOTIFICATION_CAPABILITY_EX = GUID.parse("{6843da03-154a-4616-a508-44371295f96b}");
pub const ASSOCIATE_NAMERES_CONTEXT = GUID.parse("{59a38b67-d4fe-46e1-ba3c-87ea74ca3049}");
pub const WSAID_CONNECTEX = GUID{
.Data1 = 0x25a207b9,
.Data2 = 0xddf3,
.Data3 = 0x4660,
.Data4 = [8]u8{ 0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e },
};
pub const WSAID_ACCEPTEX = GUID{
.Data1 = 0xb5367df1,
.Data2 = 0xcbac,
.Data3 = 0x11cf,
.Data4 = [8]u8{ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 },
};
pub const WSAID_GETACCEPTEXSOCKADDRS = GUID{
.Data1 = 0xb5367df2,
.Data2 = 0xcbac,
.Data3 = 0x11cf,
.Data4 = [8]u8{ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 },
};
pub const WSAID_WSARECVMSG = GUID{
.Data1 = 0xf689d7c8,
.Data2 = 0x6f1f,
.Data3 = 0x436b,
.Data4 = [8]u8{ 0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22 },
};
pub const WSAID_WSAPOLL = GUID{
.Data1 = 0x18C76F85,
.Data2 = 0xDC66,
.Data3 = 0x4964,
.Data4 = [8]u8{ 0x97, 0x2E, 0x23, 0xC2, 0x72, 0x38, 0x31, 0x2B },
};
pub const WSAID_WSASENDMSG = GUID{
.Data1 = 0xa441e712,
.Data2 = 0x754f,
.Data3 = 0x43ca,
.Data4 = [8]u8{ 0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d },
};
pub const TCP_INITIAL_RTO_DEFAULT_RTT = 0;
pub const TCP_INITIAL_RTO_DEFAULT_MAX_SYN_RETRANSMISSIONS = 0;
pub const SOCKET_SETTINGS_GUARANTEE_ENCRYPTION = 1;
@ -485,6 +529,7 @@ pub const IOC_UNIX = 0;
pub const IOC_WS2 = 134217728;
pub const IOC_PROTOCOL = 268435456;
pub const IOC_VENDOR = 402653184;
pub const SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_OUT | IOC_IN | IOC_WS2 | 6;
pub const SIO_BSP_HANDLE = IOC_OUT | IOC_WS2 | 27;
pub const SIO_BSP_HANDLE_SELECT = IOC_OUT | IOC_WS2 | 28;
pub const SIO_BSP_HANDLE_POLL = IOC_OUT | IOC_WS2 | 29;
@ -1115,9 +1160,9 @@ pub const LPFN_GETACCEPTEXSOCKADDRS = fn (
RemoteSockaddrLength: *i32,
) callconv(WINAPI) void;
pub const LFN_WSASENDMSG = fn (
pub const LPFN_WSASENDMSG = fn (
s: SOCKET,
lpMsg: *WSAMSG_const,
lpMsg: *const WSAMSG_const,
dwFlags: u32,
lpNumberOfBytesSent: ?*u32,
lpOverlapped: ?*OVERLAPPED,
@ -1927,7 +1972,7 @@ pub extern "ws2_32" fn WSAHtons(
pub extern "ws2_32" fn WSAIoctl(
s: SOCKET,
dwIoControlCode: u32,
lpvInBuffer: ?*c_void,
lpvInBuffer: ?*const c_void,
cbInBuffer: u32,
lpvOutbuffer: ?*c_void,
cbOutbuffer: u32,
@ -1992,7 +2037,7 @@ pub extern "ws2_32" fn WSASend(
s: SOCKET,
lpBuffers: [*]WSABUF,
dwBufferCount: u32,
lpNumberOfBytesSent: ?*U32,
lpNumberOfBytesSent: ?*u32,
dwFlags: u32,
lpOverlapped: ?*OVERLAPPED,
lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
@ -2000,7 +2045,7 @@ pub extern "ws2_32" fn WSASend(
pub extern "ws2_32" fn WSASendMsg(
s: SOCKET,
lpMsg: *WSAMSG_const,
lpMsg: *const WSAMSG_const,
dwFlags: u32,
lpNumberOfBytesSent: ?*u32,
lpOverlapped: ?*OVERLAPPED,

View File

@ -13,105 +13,111 @@ const mem = std.mem;
const time = std.time;
const builtin = std.builtin;
/// Import in a `Socket` abstraction depending on the platform we are compiling against.
pub usingnamespace switch (builtin.os.tag) {
.windows => @import("socket_windows.zig"),
else => @import("socket_posix.zig"),
};
/// A generic, cross-platform socket abstraction.
pub const Socket = struct {
/// A socket-address pair.
pub const Connection = struct {
socket: Socket,
address: Socket.Address,
/// A common subset of shared structs across cross-platform abstractions over socket syscalls.
pub fn Mixin(comptime Self: type) type {
return struct {
/// A socket-address pair.
pub const Connection = struct {
socket: Self,
address: Self.Address,
/// Enclose a socket and address into a socket-address pair.
pub fn from(socket: Self, address: Self.Address) Self.Connection {
return .{ .socket = socket, .address = address };
}
};
/// A generic socket address abstraction. It is safe to directly access and modify
/// the fields of a `Self.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) Self.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) Self.Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Parses a `sockaddr` into a generic socket address.
pub fn fromNative(address: *align(4) const os.sockaddr) Self.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 Self.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 Self.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: Self.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: Self.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: Self.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 }),
}
}
};
/// Enclose a socket and address into a socket-address pair.
pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
return .{ .socket = socket, .address = address };
}
};
}
/// A generic socket address abstraction. It is safe to directly access and modify
/// the fields of a `Socket.Address`.
pub const Address = union(enum) {
ipv4: net.IPv4.Address,
ipv6: net.IPv6.Address,
/// Instantiate a new address with a IPv4 host and port.
pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
return .{ .ipv4 = .{ .host = host, .port = port } };
}
/// Instantiate a new address with a IPv6 host and port.
pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Parses a `sockaddr` into a generic socket address.
pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
switch (address.family) {
os.AF_INET => {
const info = @ptrCast(*const os.sockaddr_in, address);
const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv4(host, port);
},
os.AF_INET6 => {
const info = @ptrCast(*const os.sockaddr_in6, address);
const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv6(host, port);
},
else => unreachable,
}
}
/// Encodes a generic socket address into an extern union that may be reliably
/// casted into a `sockaddr` which may be passed into socket syscalls.
pub fn toNative(self: Socket.Address) extern union {
ipv4: os.sockaddr_in,
ipv6: os.sockaddr_in6,
} {
return switch (self) {
.ipv4 => |address| .{
.ipv4 = .{
.addr = @bitCast(u32, address.host.octets),
.port = mem.nativeToBig(u16, address.port),
},
},
.ipv6 => |address| .{
.ipv6 = .{
.addr = address.host.octets,
.port = mem.nativeToBig(u16, address.port),
.scope_id = address.host.scope_id,
.flowinfo = 0,
},
},
};
}
/// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
pub fn getNativeSize(self: Socket.Address) u32 {
return switch (self) {
.ipv4 => @sizeOf(os.sockaddr_in),
.ipv6 => @sizeOf(os.sockaddr_in6),
};
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: Socket.Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// The underlying handle of a socket.
fd: os.socket_t,
/// 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 (builtin.os.tag) {
.windows => @import("socket_windows.zig"),
else => @import("socket_posix.zig"),
}.Mixin(Socket);
};

View File

@ -10,232 +10,242 @@ const os = std.os;
const mem = std.mem;
const time = std.time;
pub const Socket = struct {
/// Import in `Socket.Address` and `Socket.Connection`.
pub usingnamespace @import("socket.zig").Mixin(Socket);
pub fn Mixin(comptime Socket: type) type {
return struct {
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
}
/// The underlying handle of a socket.
fd: os.socket_t,
/// Closes the socket.
pub fn deinit(self: Socket) void {
os.closeSocket(self.fd);
}
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
}
/// 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);
}
/// Enclose a socket abstraction over an existing socket file descriptor.
pub fn from(fd: os.socket_t) Socket {
return Socket{ .fd = fd };
}
/// 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());
}
/// Closes the socket.
pub fn deinit(self: Socket) void {
os.closeSocket(self.fd);
}
/// 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);
}
/// 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);
}
/// 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());
}
/// 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());
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
/// 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);
}
const socket = Socket{ .fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags) };
const socket_address = Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
/// 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());
}
return Socket.Connection.from(socket, socket_address);
}
/// Accept a pending incoming connection queued to the kernel backlog
/// of the socket.
pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
/// 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);
}
const socket = Socket{ .fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags) };
const socket_address = Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
/// 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);
}
return Socket.Connection.from(socket, socket_address);
}
/// 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 writeVectorized(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return os.sendmsg(self.fd, msg, 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: Socket, buf: []u8, flags: u32) !usize {
return os.recv(self.fd, buf, 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 readVectorized(self: Socket, msg: *os.msghdr, flags: u32) !usize {
if (comptime @hasDecl(os.system, "recvmsg")) {
while (true) {
const rc = os.system.recvmsg(self.fd, msg, flags);
return switch (os.errno(rc)) {
0 => @intCast(usize, rc),
os.EBADF => unreachable, // always a race condition
os.EFAULT => unreachable,
os.EINVAL => unreachable,
os.ENOTCONN => unreachable,
os.ENOTSOCK => unreachable,
os.EINTR => continue,
os.EAGAIN => error.WouldBlock,
os.ENOMEM => error.SystemResources,
os.ECONNREFUSED => error.ConnectionRefused,
os.ECONNRESET => error.ConnectionResetByPeer,
else => |err| os.unexpectedErrno(err),
};
}
}
return error.NotSupported;
}
/// 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);
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
/// 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 writeVectorized(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return os.sendmsg(self.fd, msg, flags);
}
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
/// 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 readVectorized(self: Socket, msg: *os.msghdr, flags: u32) !usize {
return error.NotImplemented;
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return os.getsockoptError(self.fd);
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
/// Query the address that the socket is connected to.
pub fn getRemoteAddress(self: Socket) !Socket.Address {
var address: os.sockaddr_storage = undefined;
var address_len: u32 = @sizeOf(os.sockaddr_storage);
try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
}
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
/// Query and return the latest cached error on the socket.
pub fn getError(self: Socket) !void {
return os.getsockoptError(self.fd);
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
/// 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_SNDBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => 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);
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
var value: u32 = undefined;
var value_len: u32 = @sizeOf(u32);
/// 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 (comptime @hasDecl(os, "SO_LINGER")) {
const settings = extern struct {
l_onoff: c_int,
l_linger: c_int,
}{
.l_onoff = @intCast(c_int, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| @intCast(c_int, seconds) else 0,
};
const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len);
return switch (os.errno(rc)) {
0 => value,
os.EBADF => error.BadFileDescriptor,
os.EFAULT => error.InvalidAddressSpace,
os.EINVAL => error.InvalidSocketOption,
os.ENOPROTOOPT => error.UnknownSocketOption,
os.ENOTSOCK => error.NotASocket,
else => |err| os.unexpectedErrno(err),
};
}
return self.setOption(os.SOL_SOCKET, os.SO_LINGER, mem.asBytes(&settings));
}
/// 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);
}
return error.UnsupportedSocketOption;
}
/// 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 (comptime @hasDecl(os, "SO_LINGER")) {
const settings = extern struct {
l_onoff: c_int,
l_linger: c_int,
}{
.l_onoff = @intCast(c_int, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| @intCast(c_int, seconds) else 0,
/// 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 (comptime @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 (comptime @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 (comptime @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_LINGER, mem.asBytes(&settings));
return self.setOption(os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
}
return error.UnsupportedSocketOption;
}
/// 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),
};
/// 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 (comptime @hasDecl(os, "SO_KEEPALIVE")) {
return self.setOption(os.SOL_SOCKET, os.SO_KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
return self.setOption(os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
}
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 (comptime @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 (comptime @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

@ -13,386 +13,438 @@ const mem = std.mem;
const windows = std.os.windows;
const ws2_32 = windows.ws2_32;
pub const Socket = struct {
/// Import in `Socket.Address` and `Socket.Connection`.
pub usingnamespace @import("socket.zig").Mixin(Socket);
pub fn Mixin(comptime Socket: type) type {
return struct {
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
var filtered_socket_type = socket_type & ~@as(u32, os.SOCK_CLOEXEC);
/// The underlying handle of a socket.
fd: os.socket_t,
var filtered_flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED;
if (socket_type & os.SOCK_CLOEXEC != 0) {
filtered_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
}
/// Open a new socket.
pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
var filtered_socket_type = socket_type & ~@as(u32, os.SOCK_CLOEXEC);
const fd = ws2_32.WSASocketW(
@intCast(i32, domain),
@intCast(i32, filtered_socket_type),
@intCast(i32, protocol),
null,
0,
filtered_flags,
);
if (fd == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => {
_ = try windows.WSAStartup(2, 2);
return Socket.init(domain, socket_type, protocol);
},
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENOBUFS => error.SystemResources,
.WSAEPROTONOSUPPORT => error.ProtocolNotSupported,
else => |err| windows.unexpectedWSAError(err),
};
}
var filtered_flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED;
if (socket_type & os.SOCK_CLOEXEC != 0) {
filtered_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
return Socket{ .fd = fd };
}
const fd = ws2_32.WSASocketW(
@intCast(i32, domain),
@intCast(i32, filtered_socket_type),
@intCast(i32, protocol),
null,
0,
filtered_flags,
);
if (fd == ws2_32.INVALID_SOCKET) {
return switch (ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => {
_ = try windows.WSAStartup(2, 2);
return Socket.init(domain, socket_type, protocol);
},
.WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
.WSAEMFILE => error.ProcessFdQuotaExceeded,
.WSAENOBUFS => error.SystemResources,
.WSAEPROTONOSUPPORT => error.ProtocolNotSupported,
else => |err| windows.unexpectedWSAError(err),
/// 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: u32) !Socket.Connection {
var address: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_storage);
const rc = ws2_32.accept(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == 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(rc);
const socket_address = Socket.Address.fromNative(@ptrCast(*ws2_32.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 {
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 writeVectorized(self: Socket, msg: ws2_32.msghdr_const, 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 readVectorized(self: Socket, msg: *ws2_32.msghdr, flags: u32) !usize {
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: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_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: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_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 {
return {};
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
return 0;
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
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,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAESHUTDOWN => return error.AlreadyShutdown,
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 = ws2_32.linger{
.l_onoff = @as(u16, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| seconds else 0,
};
return self.setOption(ws2_32.SOL_SOCKET, ws2_32.SO_LINGER, mem.asBytes(&settings));
}
return Socket{ .fd = fd };
}
/// 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 {
_ = 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: u32) !Socket.Connection {
var address: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_storage);
const rc = ws2_32.accept(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
if (rc == 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),
};
/// 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))));
}
const socket = Socket.from(rc);
const socket_address = Socket.Address.fromNative(@ptrCast(*ws2_32.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 {
var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }};
var flags_ = flags;
const rc = ws2_32.WSARecv(self.fd, bufs, 1, null, &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),
};
/// 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))));
}
return @intCast(usize, rc);
}
/// 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 = buf.ptr }};
var flags_ = flags;
const rc = ws2_32.WSASend(self.fd, bufs, 1, null, &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),
};
/// 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);
}
return @intCast(usize, rc);
}
/// 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 writeVectorized(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
return error.NotImplemented;
}
/// 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 readVectorized(self: Socket, msg: *os.msghdr, flags: u32) !usize {
return error.NotImplemented;
}
/// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_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),
};
/// 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));
}
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: ws2_32.sockaddr_storage = undefined;
var address_len: c_int = @sizeOf(ws2_32.sockaddr_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),
};
/// 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));
}
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 {};
}
/// Query the read buffer size of the socket.
pub fn getReadBufferSize(self: Socket) !u32 {
return 0;
}
/// Query the write buffer size of the socket.
pub fn getWriteBufferSize(self: Socket) !u32 {
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,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAESHUTDOWN => return error.AlreadyShutdown,
else => |err| windows.unexpectedWSAError(err),
};
/// 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));
}
}
/// 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 = ws2_32.linger{
.l_onoff = @as(u16, @boolToInt(timeout_seconds != null)),
.l_linger = if (timeout_seconds) |seconds| seconds else 0,
};
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));
}
};
/// 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));
}
};
}