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