From 77f8a9ae223370d43b4c02ff30f936b41e412535 Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 9 May 2021 15:43:56 +0900 Subject: [PATCH] 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. --- lib/std/os/windows.zig | 32 ++ lib/std/os/windows/ws2_32.zig | 55 ++- lib/std/x/os/socket.zig | 206 +++++---- lib/std/x/os/socket_posix.zig | 408 +++++++++-------- lib/std/x/os/socket_windows.zig | 778 +++++++++++++++++--------------- 5 files changed, 812 insertions(+), 667 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 8e5b6347d1..12238ba776 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -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 { diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index a76baa0acc..62b9a726fe 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -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, diff --git a/lib/std/x/os/socket.zig b/lib/std/x/os/socket.zig index 096effaef2..963a1adca6 100644 --- a/lib/std/x/os/socket.zig +++ b/lib/std/x/os/socket.zig @@ -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); +}; diff --git a/lib/std/x/os/socket_posix.zig b/lib/std/x/os/socket_posix.zig index 904938bfc8..1e54c5c7a2 100644 --- a/lib/std/x/os/socket_posix.zig +++ b/lib/std/x/os/socket_posix.zig @@ -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)); - } -}; + }; +} diff --git a/lib/std/x/os/socket_windows.zig b/lib/std/x/os/socket_windows.zig index 8a0c9b43ce..2f739be5b4 100644 --- a/lib/std/x/os/socket_windows.zig +++ b/lib/std/x/os/socket_windows.zig @@ -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)); + } + }; +}