From cd0d514643404103a83881fc4d7c46674ed9f991 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 11 Dec 2022 15:35:15 -0700 Subject: [PATCH] remove the experimental std.x namespace Playtime is over. I'm working on networking now. --- lib/std/c.zig | 4 +- lib/std/c/darwin.zig | 11 +- lib/std/c/dragonfly.zig | 14 +- lib/std/c/freebsd.zig | 12 +- lib/std/c/haiku.zig | 12 +- lib/std/c/netbsd.zig | 12 +- lib/std/c/openbsd.zig | 12 +- lib/std/c/solaris.zig | 11 +- lib/std/os.zig | 4 +- lib/std/os/linux.zig | 53 +- lib/std/os/linux/seccomp.zig | 18 +- lib/std/os/windows/ws2_32.zig | 19 +- lib/std/std.zig | 1 - lib/std/x.zig | 19 - lib/std/x/net/bpf.zig | 1003 ------------------------------- lib/std/x/net/ip.zig | 57 -- lib/std/x/net/tcp.zig | 447 -------------- lib/std/x/os/io.zig | 224 ------- lib/std/x/os/net.zig | 605 ------------------- lib/std/x/os/socket.zig | 320 ---------- lib/std/x/os/socket_posix.zig | 275 --------- lib/std/x/os/socket_windows.zig | 458 -------------- 22 files changed, 143 insertions(+), 3448 deletions(-) delete mode 100644 lib/std/x.zig delete mode 100644 lib/std/x/net/bpf.zig delete mode 100644 lib/std/x/net/ip.zig delete mode 100644 lib/std/x/net/tcp.zig delete mode 100644 lib/std/x/os/io.zig delete mode 100644 lib/std/x/os/net.zig delete mode 100644 lib/std/x/os/socket.zig delete mode 100644 lib/std/x/os/socket_posix.zig delete mode 100644 lib/std/x/os/socket_windows.zig diff --git a/lib/std/c.zig b/lib/std/c.zig index 5f03f1c619..212b8e2d4d 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -206,7 +206,7 @@ pub extern "c" fn sendto( dest_addr: ?*const c.sockaddr, addrlen: c.socklen_t, ) isize; -pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const std.x.os.Socket.Message, flags: c_int) isize; +pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const c.msghdr_const, flags: u32) isize; pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize; pub extern "c" fn recvfrom( @@ -217,7 +217,7 @@ pub extern "c" fn recvfrom( noalias src_addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t, ) isize; -pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *std.x.os.Socket.Message, flags: c_int) isize; +pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *c.msghdr, flags: u32) isize; pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int; pub extern "c" fn getdirentries(fd: c.fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index b68f04379f..9c5ac1e93a 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -1007,7 +1007,16 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [126]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), family: sa_family_t = AF.INET, diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 2410310fc7..f5471c3145 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -1,5 +1,6 @@ const builtin = @import("builtin"); const std = @import("../std.zig"); +const assert = std.debug.assert; const maxInt = std.math.maxInt; const iovec = std.os.iovec; @@ -476,11 +477,20 @@ pub const CLOCK = struct { pub const sockaddr = extern struct { len: u8, - family: u8, + family: sa_family_t, data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [126]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index c4bd4a44a7..f3858317df 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; const iovec = std.os.iovec; @@ -401,7 +402,16 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [126]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 86b9f25902..9c4f8460de 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; const iovec = std.os.iovec; @@ -339,7 +340,16 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [126]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index d9bf925c17..805bb1bd3e 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; const iovec = std.os.iovec; @@ -481,7 +482,16 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [126]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 83aed68483..74f824cae3 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const maxInt = std.math.maxInt; const builtin = @import("builtin"); const iovec = std.os.iovec; @@ -372,7 +373,16 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 256; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + len: u8 align(8), + family: sa_family_t, + padding: [254]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index cbeeb5fb42..fe60c426e5 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; const iovec = std.os.iovec; @@ -435,7 +436,15 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 256; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + family: sa_family_t align(8), + padding: [254]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; pub const in = extern struct { family: sa_family_t = AF.INET, diff --git a/lib/std/os.zig b/lib/std/os.zig index b0884cef05..a47e3d0068 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -5616,11 +5616,11 @@ pub fn sendmsg( /// The file descriptor of the sending socket. sockfd: socket_t, /// Message header and iovecs - msg: msghdr_const, + msg: *const msghdr_const, flags: u32, ) SendMsgError!usize { while (true) { - const rc = system.sendmsg(sockfd, @ptrCast(*const std.x.os.Socket.Message, &msg), @intCast(c_int, flags)); + const rc = system.sendmsg(sockfd, msg, flags); if (builtin.os.tag == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index ecb8a21d7a..d9d5fb3204 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1226,11 +1226,14 @@ pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noal return syscall5(.getsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen)); } -pub fn sendmsg(fd: i32, msg: *const std.x.os.Socket.Message, flags: c_int) usize { +pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize { + const fd_usize = @bitCast(usize, @as(isize, fd)); + const msg_usize = @ptrToInt(msg); if (native_arch == .x86) { - return socketcall(SC.sendmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) }); + return socketcall(SC.sendmsg, &[3]usize{ fd_usize, msg_usize, flags }); + } else { + return syscall3(.sendmsg, fd_usize, msg_usize, flags); } - return syscall3(.sendmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags))); } pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize { @@ -1274,24 +1277,42 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize } pub fn connect(fd: i32, addr: *const anyopaque, len: socklen_t) usize { + const fd_usize = @bitCast(usize, @as(isize, fd)); + const addr_usize = @ptrToInt(addr); if (native_arch == .x86) { - return socketcall(SC.connect, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len }); + return socketcall(SC.connect, &[3]usize{ fd_usize, addr_usize, len }); + } else { + return syscall3(.connect, fd_usize, addr_usize, len); } - return syscall3(.connect, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len); } -pub fn recvmsg(fd: i32, msg: *std.x.os.Socket.Message, flags: c_int) usize { +pub fn recvmsg(fd: i32, msg: *msghdr, flags: u32) usize { + const fd_usize = @bitCast(usize, @as(isize, fd)); + const msg_usize = @ptrToInt(msg); if (native_arch == .x86) { - return socketcall(SC.recvmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) }); + return socketcall(SC.recvmsg, &[3]usize{ fd_usize, msg_usize, flags }); + } else { + return syscall3(.recvmsg, fd_usize, msg_usize, flags); } - return syscall3(.recvmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags))); } -pub fn recvfrom(fd: i32, noalias buf: [*]u8, len: usize, flags: u32, noalias addr: ?*sockaddr, noalias alen: ?*socklen_t) usize { +pub fn recvfrom( + fd: i32, + noalias buf: [*]u8, + len: usize, + flags: u32, + noalias addr: ?*sockaddr, + noalias alen: ?*socklen_t, +) usize { + const fd_usize = @bitCast(usize, @as(isize, fd)); + const buf_usize = @ptrToInt(buf); + const addr_usize = @ptrToInt(addr); + const alen_usize = @ptrToInt(alen); if (native_arch == .x86) { - return socketcall(SC.recvfrom, &[6]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen) }); + return socketcall(SC.recvfrom, &[6]usize{ fd_usize, buf_usize, len, flags, addr_usize, alen_usize }); + } else { + return syscall6(.recvfrom, fd_usize, buf_usize, len, flags, addr_usize, alen_usize); } - return syscall6(.recvfrom, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen)); } pub fn shutdown(fd: i32, how: i32) usize { @@ -3219,7 +3240,15 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + family: sa_family_t align(8), + padding: [SS_MAXSIZE - @sizeOf(sa_family_t)]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; /// IPv4 socket address pub const in = extern struct { diff --git a/lib/std/os/linux/seccomp.zig b/lib/std/os/linux/seccomp.zig index fd002e7416..03a96633f8 100644 --- a/lib/std/os/linux/seccomp.zig +++ b/lib/std/os/linux/seccomp.zig @@ -6,16 +6,14 @@ //! isn't that useful for general-purpose applications, and so a mode that //! utilizes user-supplied filters mode was added. //! -//! Seccomp filters are classic BPF programs, which means that all the -//! information under `std.x.net.bpf` applies here as well. Conceptually, a -//! seccomp program is attached to the kernel and is executed on each syscall. -//! The "packet" being validated is the `data` structure, and the verdict is an -//! action that the kernel performs on the calling process. The actions are -//! variations on a "pass" or "fail" result, where a pass allows the syscall to -//! continue and a fail blocks the syscall and returns some sort of error value. -//! See the full list of actions under ::RET for more information. Finally, only -//! word-sized, absolute loads (`ld [k]`) are supported to read from the `data` -//! structure. +//! Seccomp filters are classic BPF programs. Conceptually, a seccomp program +//! is attached to the kernel and is executed on each syscall. The "packet" +//! being validated is the `data` structure, and the verdict is an action that +//! the kernel performs on the calling process. The actions are variations on a +//! "pass" or "fail" result, where a pass allows the syscall to continue and a +//! fail blocks the syscall and returns some sort of error value. See the full +//! list of actions under ::RET for more information. Finally, only word-sized, +//! absolute loads (`ld [k]`) are supported to read from the `data` structure. //! //! There are some issues with the filter API that have traditionally made //! writing them a pain: diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index 90e1422fd2..b4d18264f3 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -1,4 +1,5 @@ const std = @import("../../std.zig"); +const assert = std.debug.assert; const windows = std.os.windows; const WINAPI = windows.WINAPI; @@ -1106,7 +1107,15 @@ pub const sockaddr = extern struct { data: [14]u8, pub const SS_MAXSIZE = 128; - pub const storage = std.x.os.Socket.Address.Native.Storage; + pub const storage = extern struct { + family: ADDRESS_FAMILY align(8), + padding: [SS_MAXSIZE - @sizeOf(ADDRESS_FAMILY)]u8 = undefined, + + comptime { + assert(@sizeOf(storage) == SS_MAXSIZE); + assert(@alignOf(storage) == 8); + } + }; /// IPv4 socket address pub const in = extern struct { @@ -1207,7 +1216,7 @@ pub const LPFN_GETACCEPTEXSOCKADDRS = *const fn ( pub const LPFN_WSASENDMSG = *const fn ( s: SOCKET, - lpMsg: *const std.x.os.Socket.Message, + lpMsg: *const WSAMSG_const, dwFlags: u32, lpNumberOfBytesSent: ?*u32, lpOverlapped: ?*OVERLAPPED, @@ -1216,7 +1225,7 @@ pub const LPFN_WSASENDMSG = *const fn ( pub const LPFN_WSARECVMSG = *const fn ( s: SOCKET, - lpMsg: *std.x.os.Socket.Message, + lpMsg: *WSAMSG, lpdwNumberOfBytesRecv: ?*u32, lpOverlapped: ?*OVERLAPPED, lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, @@ -2090,7 +2099,7 @@ pub extern "ws2_32" fn WSASend( pub extern "ws2_32" fn WSASendMsg( s: SOCKET, - lpMsg: *const std.x.os.Socket.Message, + lpMsg: *WSAMSG_const, dwFlags: u32, lpNumberOfBytesSent: ?*u32, lpOverlapped: ?*OVERLAPPED, @@ -2099,7 +2108,7 @@ pub extern "ws2_32" fn WSASendMsg( pub extern "ws2_32" fn WSARecvMsg( s: SOCKET, - lpMsg: *std.x.os.Socket.Message, + lpMsg: *WSAMSG, lpdwNumberOfBytesRecv: ?*u32, lpOverlapped: ?*OVERLAPPED, lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, diff --git a/lib/std/std.zig b/lib/std/std.zig index 1b4217b506..4bfb44d12f 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -90,7 +90,6 @@ pub const tz = @import("tz.zig"); pub const unicode = @import("unicode.zig"); pub const valgrind = @import("valgrind.zig"); pub const wasm = @import("wasm.zig"); -pub const x = @import("x.zig"); pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); diff --git a/lib/std/x.zig b/lib/std/x.zig deleted file mode 100644 index 64caf324ed..0000000000 --- a/lib/std/x.zig +++ /dev/null @@ -1,19 +0,0 @@ -const std = @import("std.zig"); - -pub const os = struct { - pub const Socket = @import("x/os/socket.zig").Socket; - pub usingnamespace @import("x/os/io.zig"); - pub usingnamespace @import("x/os/net.zig"); -}; - -pub const net = struct { - pub const ip = @import("x/net/ip.zig"); - pub const tcp = @import("x/net/tcp.zig"); - pub const bpf = @import("x/net/bpf.zig"); -}; - -test { - inline for (.{ os, net }) |module| { - std.testing.refAllDecls(module); - } -} diff --git a/lib/std/x/net/bpf.zig b/lib/std/x/net/bpf.zig deleted file mode 100644 index bee930c332..0000000000 --- a/lib/std/x/net/bpf.zig +++ /dev/null @@ -1,1003 +0,0 @@ -//! This package provides instrumentation for creating Berkeley Packet Filter[1] -//! (BPF) programs, along with a simulator for running them. -//! -//! BPF is a mechanism for cheap, in-kernel packet filtering. Programs are -//! attached to a network device and executed for every packet that flows -//! through it. The program must then return a verdict: the amount of packet -//! bytes that the kernel should copy into userspace. Execution speed is -//! achieved by having programs run in a limited virtual machine, which has the -//! added benefit of graceful failure in the face of buggy programs. -//! -//! The BPF virtual machine has a 32-bit word length and a small number of -//! word-sized registers: -//! -//! - The accumulator, `a`: The source/destination of arithmetic and logic -//! operations. -//! - The index register, `x`: Used as an offset for indirect memory access and -//! as a comparison value for conditional jumps. -//! - The scratch memory store, `M[0]..M[15]`: Used for saving the value of a/x -//! for later use. -//! -//! The packet being examined is an array of bytes, and is addressed using plain -//! array subscript notation, e.g. [10] for the byte at offset 10. An implicit -//! program counter, `pc`, is intialized to zero and incremented for each instruction. -//! -//! The machine has a fixed instruction set with the following form, where the -//! numbers represent bit length: -//! -//! ``` -//! ┌───────────┬──────┬──────┐ -//! │ opcode:16 │ jt:8 │ jt:8 │ -//! ├───────────┴──────┴──────┤ -//! │ k:32 │ -//! └─────────────────────────┘ -//! ``` -//! -//! The `opcode` indicates the instruction class and its addressing mode. -//! Opcodes are generated by performing binary addition on the 8-bit class and -//! mode constants. For example, the opcode for loading a byte from the packet -//! at X + 2, (`ldb [x + 2]`), is: -//! -//! ``` -//! LD | IND | B = 0x00 | 0x40 | 0x20 -//! = 0x60 -//! ``` -//! -//! `jt` is an offset used for conditional jumps, and increments the program -//! counter by its amount if the comparison was true. Conversely, `jf` -//! increments the counter if it was false. These fields are ignored in all -//! other cases. `k` is a generic variable used for various purposes, most -//! commonly as some sort of constant. -//! -//! This package contains opcode extensions used by different implementations, -//! where "extension" is anything outside of the original that was imported into -//! 4.4BSD[2]. These are marked with "EXTENSION", along with a list of -//! implementations that use them. -//! -//! Most of the doc-comments use the BPF assembly syntax as described in the -//! original paper[1]. For the sake of completeness, here is the complete -//! instruction set, along with the extensions: -//! -//!``` -//! opcode addressing modes -//! ld #k #len M[k] [k] [x + k] -//! ldh [k] [x + k] -//! ldb [k] [x + k] -//! ldx #k #len M[k] 4 * ([k] & 0xf) arc4random() -//! st M[k] -//! stx M[k] -//! jmp L -//! jeq #k, Lt, Lf -//! jgt #k, Lt, Lf -//! jge #k, Lt, Lf -//! jset #k, Lt, Lf -//! add #k x -//! sub #k x -//! mul #k x -//! div #k x -//! or #k x -//! and #k x -//! lsh #k x -//! rsh #k x -//! neg #k x -//! mod #k x -//! xor #k x -//! ret #k a -//! tax -//! txa -//! ``` -//! -//! Finally, a note on program design. The lack of backwards jumps leads to a -//! "return early, return often" control flow. Take for example the program -//! generated from the tcpdump filter `ip`: -//! -//! ``` -//! (000) ldh [12] ; Ethernet Packet Type -//! (001) jeq #0x86dd, 2, 7 ; ETHERTYPE_IPV6 -//! (002) ldb [20] ; IPv6 Next Header -//! (003) jeq #0x6, 10, 4 ; TCP -//! (004) jeq #0x2c, 5, 11 ; IPv6 Fragment Header -//! (005) ldb [54] ; TCP Source Port -//! (006) jeq #0x6, 10, 11 ; IPPROTO_TCP -//! (007) jeq #0x800, 8, 11 ; ETHERTYPE_IP -//! (008) ldb [23] ; IPv4 Protocol -//! (009) jeq #0x6, 10, 11 ; IPPROTO_TCP -//! (010) ret #262144 ; copy 0x40000 -//! (011) ret #0 ; skip packet -//! ``` -//! -//! Here we can make a few observations: -//! -//! - The problem "filter only tcp packets" has essentially been transformed -//! into a series of layer checks. -//! - There are two distinct branches in the code, one for validating IPv4 -//! headers and one for IPv6 headers. -//! - Most conditional jumps in these branches lead directly to the last two -//! instructions, a pass or fail. Thus the goal of a program is to find the -//! fastest route to a pass/fail comparison. -//! -//! [1]: S. McCanne and V. Jacobson, "The BSD Packet Filter: A New Architecture -//! for User-level Packet Capture", Proceedings of the 1993 Winter USENIX. -//! [2]: https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/src/sys/net/bpf.h -const std = @import("std"); -const builtin = @import("builtin"); -const native_endian = builtin.target.cpu.arch.endian(); -const mem = std.mem; -const math = std.math; -const random = std.crypto.random; -const assert = std.debug.assert; -const expectEqual = std.testing.expectEqual; -const expectError = std.testing.expectError; -const expect = std.testing.expect; - -// instruction classes -/// ld, ldh, ldb: Load data into a. -pub const LD = 0x00; -/// ldx: Load data into x. -pub const LDX = 0x01; -/// st: Store into scratch memory the value of a. -pub const ST = 0x02; -/// st: Store into scratch memory the value of x. -pub const STX = 0x03; -/// alu: Wrapping arithmetic/bitwise operations on a using the value of k/x. -pub const ALU = 0x04; -/// jmp, jeq, jgt, je, jset: Increment the program counter based on a comparison -/// between k/x and the accumulator. -pub const JMP = 0x05; -/// ret: Return a verdict using the value of k/the accumulator. -pub const RET = 0x06; -/// tax, txa: Register value copying between X and a. -pub const MISC = 0x07; - -// Size of data to be loaded from the packet. -/// ld: 32-bit full word. -pub const W = 0x00; -/// ldh: 16-bit half word. -pub const H = 0x08; -/// ldb: Single byte. -pub const B = 0x10; - -// Addressing modes used for loads to a/x. -/// #k: The immediate value stored in k. -pub const IMM = 0x00; -/// [k]: The value at offset k in the packet. -pub const ABS = 0x20; -/// [x + k]: The value at offset x + k in the packet. -pub const IND = 0x40; -/// M[k]: The value of the k'th scratch memory register. -pub const MEM = 0x60; -/// #len: The size of the packet. -pub const LEN = 0x80; -/// 4 * ([k] & 0xf): Four times the low four bits of the byte at offset k in the -/// packet. This is used for efficiently loading the header length of an IP -/// packet. -pub const MSH = 0xa0; -/// arc4random: 32-bit integer generated from a CPRNG (see arc4random(3)) loaded into a. -/// EXTENSION. Defined for: -/// - OpenBSD. -pub const RND = 0xc0; - -// Modifiers for different instruction classes. -/// Use the value of k for alu operations (add #k). -/// Compare against the value of k for jumps (jeq #k, Lt, Lf). -/// Return the value of k for returns (ret #k). -pub const K = 0x00; -/// Use the value of x for alu operations (add x). -/// Compare against the value of X for jumps (jeq x, Lt, Lf). -pub const X = 0x08; -/// Return the value of a for returns (ret a). -pub const A = 0x10; - -// ALU Operations on a using the value of k/x. -// All arithmetic operations are defined to overflow the value of a. -/// add: a = a + k -/// a = a + x. -pub const ADD = 0x00; -/// sub: a = a - k -/// a = a - x. -pub const SUB = 0x10; -/// mul: a = a * k -/// a = a * x. -pub const MUL = 0x20; -/// div: a = a / k -/// a = a / x. -/// Truncated division. -pub const DIV = 0x30; -/// or: a = a | k -/// a = a | x. -pub const OR = 0x40; -/// and: a = a & k -/// a = a & x. -pub const AND = 0x50; -/// lsh: a = a << k -/// a = a << x. -/// a = a << k, a = a << x. -pub const LSH = 0x60; -/// rsh: a = a >> k -/// a = a >> x. -pub const RSH = 0x70; -/// neg: a = -a. -/// Note that this isn't a binary negation, rather the value of `~a + 1`. -pub const NEG = 0x80; -/// mod: a = a % k -/// a = a % x. -/// EXTENSION. Defined for: -/// - Linux. -/// - NetBSD + Minix 3. -/// - FreeBSD and derivitives. -pub const MOD = 0x90; -/// xor: a = a ^ k -/// a = a ^ x. -/// EXTENSION. Defined for: -/// - Linux. -/// - NetBSD + Minix 3. -/// - FreeBSD and derivitives. -pub const XOR = 0xa0; - -// Jump operations using a comparison between a and x/k. -/// jmp L: pc += k. -/// No comparison done here. -pub const JA = 0x00; -/// jeq #k, Lt, Lf: pc += (a == k) ? jt : jf. -/// jeq x, Lt, Lf: pc += (a == x) ? jt : jf. -pub const JEQ = 0x10; -/// jgt #k, Lt, Lf: pc += (a > k) ? jt : jf. -/// jgt x, Lt, Lf: pc += (a > x) ? jt : jf. -pub const JGT = 0x20; -/// jge #k, Lt, Lf: pc += (a >= k) ? jt : jf. -/// jge x, Lt, Lf: pc += (a >= x) ? jt : jf. -pub const JGE = 0x30; -/// jset #k, Lt, Lf: pc += (a & k > 0) ? jt : jf. -/// jset x, Lt, Lf: pc += (a & x > 0) ? jt : jf. -pub const JSET = 0x40; - -// Miscellaneous operations/register copy. -/// tax: x = a. -pub const TAX = 0x00; -/// txa: a = x. -pub const TXA = 0x80; - -/// The 16 registers in the scratch memory store as named enums. -pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 }; -pub const MEMWORDS = 16; -pub const MAXINSNS = switch (builtin.os.tag) { - .linux => 4096, - else => 512, -}; -pub const MINBUFSIZE = 32; -pub const MAXBUFSIZE = 1 << 21; - -pub const Insn = extern struct { - opcode: u16, - jt: u8, - jf: u8, - k: u32, - - /// Implements the `std.fmt.format` API. - /// The formatting is similar to the output of tcpdump -dd. - pub fn format( - self: Insn, - comptime layout: []const u8, - opts: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = opts; - if (layout.len != 0) std.fmt.invalidFmtError(layout, self); - - try std.fmt.format( - writer, - "Insn{{ 0x{X:0<2}, {d}, {d}, 0x{X:0<8} }}", - .{ self.opcode, self.jt, self.jf, self.k }, - ); - } - - const Size = enum(u8) { - word = W, - half_word = H, - byte = B, - }; - - fn stmt(opcode: u16, k: u32) Insn { - return .{ - .opcode = opcode, - .jt = 0, - .jf = 0, - .k = k, - }; - } - - pub fn ld_imm(value: u32) Insn { - return stmt(LD | IMM, value); - } - - pub fn ld_abs(size: Size, offset: u32) Insn { - return stmt(LD | ABS | @enumToInt(size), offset); - } - - pub fn ld_ind(size: Size, offset: u32) Insn { - return stmt(LD | IND | @enumToInt(size), offset); - } - - pub fn ld_mem(reg: Scratch) Insn { - return stmt(LD | MEM, @enumToInt(reg)); - } - - pub fn ld_len() Insn { - return stmt(LD | LEN | W, 0); - } - - pub fn ld_rnd() Insn { - return stmt(LD | RND | W, 0); - } - - pub fn ldx_imm(value: u32) Insn { - return stmt(LDX | IMM, value); - } - - pub fn ldx_mem(reg: Scratch) Insn { - return stmt(LDX | MEM, @enumToInt(reg)); - } - - pub fn ldx_len() Insn { - return stmt(LDX | LEN | W, 0); - } - - pub fn ldx_msh(offset: u32) Insn { - return stmt(LDX | MSH | B, offset); - } - - pub fn st(reg: Scratch) Insn { - return stmt(ST, @enumToInt(reg)); - } - pub fn stx(reg: Scratch) Insn { - return stmt(STX, @enumToInt(reg)); - } - - const AluOp = enum(u16) { - add = ADD, - sub = SUB, - mul = MUL, - div = DIV, - @"or" = OR, - @"and" = AND, - lsh = LSH, - rsh = RSH, - mod = MOD, - xor = XOR, - }; - - const Source = enum(u16) { - k = K, - x = X, - }; - const KOrX = union(Source) { - k: u32, - x: void, - }; - - pub fn alu_neg() Insn { - return stmt(ALU | NEG, 0); - } - - pub fn alu(op: AluOp, source: KOrX) Insn { - return stmt( - ALU | @enumToInt(op) | @enumToInt(source), - if (source == .k) source.k else 0, - ); - } - - const JmpOp = enum(u16) { - jeq = JEQ, - jgt = JGT, - jge = JGE, - jset = JSET, - }; - - pub fn jmp_ja(location: u32) Insn { - return stmt(JMP | JA, location); - } - - pub fn jmp(op: JmpOp, source: KOrX, jt: u8, jf: u8) Insn { - return Insn{ - .opcode = JMP | @enumToInt(op) | @enumToInt(source), - .jt = jt, - .jf = jf, - .k = if (source == .k) source.k else 0, - }; - } - - const Verdict = enum(u16) { - k = K, - a = A, - }; - const KOrA = union(Verdict) { - k: u32, - a: void, - }; - - pub fn ret(verdict: KOrA) Insn { - return stmt( - RET | @enumToInt(verdict), - if (verdict == .k) verdict.k else 0, - ); - } - - pub fn tax() Insn { - return stmt(MISC | TAX, 0); - } - - pub fn txa() Insn { - return stmt(MISC | TXA, 0); - } -}; - -fn opcodeEqual(opcode: u16, insn: Insn) !void { - try expectEqual(opcode, insn.opcode); -} - -test "opcodes" { - try opcodeEqual(0x00, Insn.ld_imm(0)); - try opcodeEqual(0x20, Insn.ld_abs(.word, 0)); - try opcodeEqual(0x28, Insn.ld_abs(.half_word, 0)); - try opcodeEqual(0x30, Insn.ld_abs(.byte, 0)); - try opcodeEqual(0x40, Insn.ld_ind(.word, 0)); - try opcodeEqual(0x48, Insn.ld_ind(.half_word, 0)); - try opcodeEqual(0x50, Insn.ld_ind(.byte, 0)); - try opcodeEqual(0x60, Insn.ld_mem(.m0)); - try opcodeEqual(0x80, Insn.ld_len()); - try opcodeEqual(0xc0, Insn.ld_rnd()); - - try opcodeEqual(0x01, Insn.ldx_imm(0)); - try opcodeEqual(0x61, Insn.ldx_mem(.m0)); - try opcodeEqual(0x81, Insn.ldx_len()); - try opcodeEqual(0xb1, Insn.ldx_msh(0)); - - try opcodeEqual(0x02, Insn.st(.m0)); - try opcodeEqual(0x03, Insn.stx(.m0)); - - try opcodeEqual(0x04, Insn.alu(.add, .{ .k = 0 })); - try opcodeEqual(0x14, Insn.alu(.sub, .{ .k = 0 })); - try opcodeEqual(0x24, Insn.alu(.mul, .{ .k = 0 })); - try opcodeEqual(0x34, Insn.alu(.div, .{ .k = 0 })); - try opcodeEqual(0x44, Insn.alu(.@"or", .{ .k = 0 })); - try opcodeEqual(0x54, Insn.alu(.@"and", .{ .k = 0 })); - try opcodeEqual(0x64, Insn.alu(.lsh, .{ .k = 0 })); - try opcodeEqual(0x74, Insn.alu(.rsh, .{ .k = 0 })); - try opcodeEqual(0x94, Insn.alu(.mod, .{ .k = 0 })); - try opcodeEqual(0xa4, Insn.alu(.xor, .{ .k = 0 })); - try opcodeEqual(0x84, Insn.alu_neg()); - try opcodeEqual(0x0c, Insn.alu(.add, .x)); - try opcodeEqual(0x1c, Insn.alu(.sub, .x)); - try opcodeEqual(0x2c, Insn.alu(.mul, .x)); - try opcodeEqual(0x3c, Insn.alu(.div, .x)); - try opcodeEqual(0x4c, Insn.alu(.@"or", .x)); - try opcodeEqual(0x5c, Insn.alu(.@"and", .x)); - try opcodeEqual(0x6c, Insn.alu(.lsh, .x)); - try opcodeEqual(0x7c, Insn.alu(.rsh, .x)); - try opcodeEqual(0x9c, Insn.alu(.mod, .x)); - try opcodeEqual(0xac, Insn.alu(.xor, .x)); - - try opcodeEqual(0x05, Insn.jmp_ja(0)); - try opcodeEqual(0x15, Insn.jmp(.jeq, .{ .k = 0 }, 0, 0)); - try opcodeEqual(0x25, Insn.jmp(.jgt, .{ .k = 0 }, 0, 0)); - try opcodeEqual(0x35, Insn.jmp(.jge, .{ .k = 0 }, 0, 0)); - try opcodeEqual(0x45, Insn.jmp(.jset, .{ .k = 0 }, 0, 0)); - try opcodeEqual(0x1d, Insn.jmp(.jeq, .x, 0, 0)); - try opcodeEqual(0x2d, Insn.jmp(.jgt, .x, 0, 0)); - try opcodeEqual(0x3d, Insn.jmp(.jge, .x, 0, 0)); - try opcodeEqual(0x4d, Insn.jmp(.jset, .x, 0, 0)); - - try opcodeEqual(0x06, Insn.ret(.{ .k = 0 })); - try opcodeEqual(0x16, Insn.ret(.a)); - - try opcodeEqual(0x07, Insn.tax()); - try opcodeEqual(0x87, Insn.txa()); -} - -pub const Error = error{ - InvalidOpcode, - InvalidOffset, - InvalidLocation, - DivisionByZero, - NoReturn, -}; - -/// A simple implementation of the BPF virtual-machine. -/// Use this to run/debug programs. -pub fn simulate( - packet: []const u8, - filter: []const Insn, - byte_order: std.builtin.Endian, -) Error!u32 { - assert(filter.len > 0 and filter.len < MAXINSNS); - assert(packet.len < MAXBUFSIZE); - const len = @intCast(u32, packet.len); - - var a: u32 = 0; - var x: u32 = 0; - var m = mem.zeroes([MEMWORDS]u32); - var pc: usize = 0; - - while (pc < filter.len) : (pc += 1) { - const i = filter[pc]; - // Cast to a wider type to protect against overflow. - const k = @as(u64, i.k); - const remaining = filter.len - (pc + 1); - - // Do validation/error checking here to compress the second switch. - switch (i.opcode) { - LD | ABS | W => if (k + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset, - LD | ABS | H => if (k + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset, - LD | ABS | B => if (k >= packet.len) return error.InvalidOffset, - LD | IND | W => if (k + x + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset, - LD | IND | H => if (k + x + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset, - LD | IND | B => if (k + x >= packet.len) return error.InvalidOffset, - - LDX | MSH | B => if (k >= packet.len) return error.InvalidOffset, - ST, STX, LD | MEM, LDX | MEM => if (i.k >= MEMWORDS) return error.InvalidOffset, - - JMP | JA => if (remaining <= i.k) return error.InvalidOffset, - JMP | JEQ | K, - JMP | JGT | K, - JMP | JGE | K, - JMP | JSET | K, - JMP | JEQ | X, - JMP | JGT | X, - JMP | JGE | X, - JMP | JSET | X, - => if (remaining <= i.jt or remaining <= i.jf) return error.InvalidLocation, - else => {}, - } - switch (i.opcode) { - LD | IMM => a = i.k, - LD | MEM => a = m[i.k], - LD | LEN | W => a = len, - LD | RND | W => a = random.int(u32), - LD | ABS | W => a = mem.readInt(u32, packet[i.k..][0..@sizeOf(u32)], byte_order), - LD | ABS | H => a = mem.readInt(u16, packet[i.k..][0..@sizeOf(u16)], byte_order), - LD | ABS | B => a = packet[i.k], - LD | IND | W => a = mem.readInt(u32, packet[i.k + x ..][0..@sizeOf(u32)], byte_order), - LD | IND | H => a = mem.readInt(u16, packet[i.k + x ..][0..@sizeOf(u16)], byte_order), - LD | IND | B => a = packet[i.k + x], - - LDX | IMM => x = i.k, - LDX | MEM => x = m[i.k], - LDX | LEN | W => x = len, - LDX | MSH | B => x = @as(u32, @truncate(u4, packet[i.k])) << 2, - - ST => m[i.k] = a, - STX => m[i.k] = x, - - ALU | ADD | K => a +%= i.k, - ALU | SUB | K => a -%= i.k, - ALU | MUL | K => a *%= i.k, - ALU | DIV | K => a = try math.divTrunc(u32, a, i.k), - ALU | OR | K => a |= i.k, - ALU | AND | K => a &= i.k, - ALU | LSH | K => a = math.shl(u32, a, i.k), - ALU | RSH | K => a = math.shr(u32, a, i.k), - ALU | MOD | K => a = try math.mod(u32, a, i.k), - ALU | XOR | K => a ^= i.k, - ALU | ADD | X => a +%= x, - ALU | SUB | X => a -%= x, - ALU | MUL | X => a *%= x, - ALU | DIV | X => a = try math.divTrunc(u32, a, x), - ALU | OR | X => a |= x, - ALU | AND | X => a &= x, - ALU | LSH | X => a = math.shl(u32, a, x), - ALU | RSH | X => a = math.shr(u32, a, x), - ALU | MOD | X => a = try math.mod(u32, a, x), - ALU | XOR | X => a ^= x, - ALU | NEG => a = @bitCast(u32, -%@bitCast(i32, a)), - - JMP | JA => pc += i.k, - JMP | JEQ | K => pc += if (a == i.k) i.jt else i.jf, - JMP | JGT | K => pc += if (a > i.k) i.jt else i.jf, - JMP | JGE | K => pc += if (a >= i.k) i.jt else i.jf, - JMP | JSET | K => pc += if (a & i.k > 0) i.jt else i.jf, - JMP | JEQ | X => pc += if (a == x) i.jt else i.jf, - JMP | JGT | X => pc += if (a > x) i.jt else i.jf, - JMP | JGE | X => pc += if (a >= x) i.jt else i.jf, - JMP | JSET | X => pc += if (a & x > 0) i.jt else i.jf, - - RET | K => return i.k, - RET | A => return a, - - MISC | TAX => x = a, - MISC | TXA => a = x, - else => return error.InvalidOpcode, - } - } - - return error.NoReturn; -} - -// This program is the BPF form of the tcpdump filter: -// -// tcpdump -dd 'ip host mirror.internode.on.net and tcp port ftp-data' -// -// As of January 2022, mirror.internode.on.net resolves to 150.101.135.3 -// -// For reference, here's what it looks like in BPF assembler. -// Note that the jumps are used for TCP/IP layer checks. -// -// ``` -// ldh [12] (#proto) -// jeq #0x0800 (ETHERTYPE_IP), L1, fail -// L1: ld [26] -// jeq #150.101.135.3, L2, dest -// dest: ld [30] -// jeq #150.101.135.3, L2, fail -// L2: ldb [23] -// jeq #0x6 (IPPROTO_TCP), L3, fail -// L3: ldh [20] -// jset #0x1fff, fail, plen -// plen: ldx 4 * ([14] & 0xf) -// ldh [x + 14] -// jeq #0x14 (FTP), pass, dstp -// dstp: ldh [x + 16] -// jeq #0x14 (FTP), pass, fail -// pass: ret #0x40000 -// fail: ret #0 -// ``` -const tcpdump_filter = [_]Insn{ - Insn.ld_abs(.half_word, 12), - Insn.jmp(.jeq, .{ .k = 0x800 }, 0, 14), - Insn.ld_abs(.word, 26), - Insn.jmp(.jeq, .{ .k = 0x96658703 }, 2, 0), - Insn.ld_abs(.word, 30), - Insn.jmp(.jeq, .{ .k = 0x96658703 }, 0, 10), - Insn.ld_abs(.byte, 23), - Insn.jmp(.jeq, .{ .k = 0x6 }, 0, 8), - Insn.ld_abs(.half_word, 20), - Insn.jmp(.jset, .{ .k = 0x1fff }, 6, 0), - Insn.ldx_msh(14), - Insn.ld_ind(.half_word, 14), - Insn.jmp(.jeq, .{ .k = 0x14 }, 2, 0), - Insn.ld_ind(.half_word, 16), - Insn.jmp(.jeq, .{ .k = 0x14 }, 0, 1), - Insn.ret(.{ .k = 0x40000 }), - Insn.ret(.{ .k = 0 }), -}; - -// This packet is the output of `ls` on mirror.internode.on.net:/, captured -// using the filter above. -// -// zig fmt: off -const ftp_data = [_]u8{ - // ethernet - 14 bytes: IPv4(0x0800) from a4:71:74:ad:4b:f0 -> de:ad:be:ef:f0:0f - 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0f, 0xa4, 0x71, 0x74, 0xad, 0x4b, 0xf0, 0x08, 0x00, - // IPv4 - 20 bytes: TCP data from 150.101.135.3 -> 192.168.1.3 - 0x45, 0x00, 0x01, 0xf2, 0x70, 0x3b, 0x40, 0x00, 0x37, 0x06, 0xf2, 0xb6, - 0x96, 0x65, 0x87, 0x03, 0xc0, 0xa8, 0x01, 0x03, - // TCP - 32 bytes: Source port: 20 (FTP). Payload = 446 bytes - 0x00, 0x14, 0x80, 0x6d, 0x35, 0x81, 0x2d, 0x40, 0x4f, 0x8a, 0x29, 0x9e, 0x80, 0x18, 0x00, 0x2e, - 0x88, 0x8d, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x0b, 0x59, 0x5d, 0x09, 0x32, 0x8b, 0x51, 0xa0 -} ++ - // Raw line-based FTP data - 446 bytes - "lrwxrwxrwx 1 root root 12 Feb 14 2012 debian -> .pub2/debian\r\n" ++ - "lrwxrwxrwx 1 root root 15 Feb 14 2012 debian-cd -> .pub2/debian-cd\r\n" ++ - "lrwxrwxrwx 1 root root 9 Mar 9 2018 linux -> pub/linux\r\n" ++ - "drwxr-xr-X 3 mirror mirror 4096 Sep 20 08:10 pub\r\n" ++ - "lrwxrwxrwx 1 root root 12 Feb 14 2012 ubuntu -> .pub2/ubuntu\r\n" ++ - "-rw-r--r-- 1 root root 1044 Jan 20 2015 welcome.msg\r\n"; -// zig fmt: on - -test "tcpdump filter" { - try expectEqual( - @as(u32, 0x40000), - try simulate(ftp_data, &tcpdump_filter, .Big), - ); -} - -fn expectPass(data: anytype, filter: []const Insn) !void { - try expectEqual( - @as(u32, 0), - try simulate(mem.asBytes(data), filter, .Big), - ); -} - -fn expectFail(expected_error: anyerror, data: anytype, filter: []const Insn) !void { - try expectError( - expected_error, - simulate(mem.asBytes(data), filter, native_endian), - ); -} - -test "simulator coverage" { - const some_data = [_]u8{ - 0xaa, 0xbb, 0xcc, 0xdd, 0x7f, - }; - - try expectPass(&some_data, &.{ - // ld #10 - // ldx #1 - // st M[0] - // stx M[1] - // fail if A != 10 - Insn.ld_imm(10), - Insn.ldx_imm(1), - Insn.st(.m0), - Insn.stx(.m1), - Insn.jmp(.jeq, .{ .k = 10 }, 1, 0), - Insn.ret(.{ .k = 1 }), - // ld [0] - // fail if A != 0xaabbccdd - Insn.ld_abs(.word, 0), - Insn.jmp(.jeq, .{ .k = 0xaabbccdd }, 1, 0), - Insn.ret(.{ .k = 2 }), - // ldh [0] - // fail if A != 0xaabb - Insn.ld_abs(.half_word, 0), - Insn.jmp(.jeq, .{ .k = 0xaabb }, 1, 0), - Insn.ret(.{ .k = 3 }), - // ldb [0] - // fail if A != 0xaa - Insn.ld_abs(.byte, 0), - Insn.jmp(.jeq, .{ .k = 0xaa }, 1, 0), - Insn.ret(.{ .k = 4 }), - // ld [x + 0] - // fail if A != 0xbbccdd7f - Insn.ld_ind(.word, 0), - Insn.jmp(.jeq, .{ .k = 0xbbccdd7f }, 1, 0), - Insn.ret(.{ .k = 5 }), - // ldh [x + 0] - // fail if A != 0xbbcc - Insn.ld_ind(.half_word, 0), - Insn.jmp(.jeq, .{ .k = 0xbbcc }, 1, 0), - Insn.ret(.{ .k = 6 }), - // ldb [x + 0] - // fail if A != 0xbb - Insn.ld_ind(.byte, 0), - Insn.jmp(.jeq, .{ .k = 0xbb }, 1, 0), - Insn.ret(.{ .k = 7 }), - // ld M[0] - // fail if A != 10 - Insn.ld_mem(.m0), - Insn.jmp(.jeq, .{ .k = 10 }, 1, 0), - Insn.ret(.{ .k = 8 }), - // ld #len - // fail if A != 5 - Insn.ld_len(), - Insn.jmp(.jeq, .{ .k = some_data.len }, 1, 0), - Insn.ret(.{ .k = 9 }), - // ld #0 - // ld arc4random() - // fail if A == 0 - Insn.ld_imm(0), - Insn.ld_rnd(), - Insn.jmp(.jgt, .{ .k = 0 }, 1, 0), - Insn.ret(.{ .k = 10 }), - // ld #3 - // ldx #10 - // st M[2] - // txa - // fail if a != x - Insn.ld_imm(3), - Insn.ldx_imm(10), - Insn.st(.m2), - Insn.txa(), - Insn.jmp(.jeq, .x, 1, 0), - Insn.ret(.{ .k = 11 }), - // ldx M[2] - // fail if A <= X - Insn.ldx_mem(.m2), - Insn.jmp(.jgt, .x, 1, 0), - Insn.ret(.{ .k = 12 }), - // ldx #len - // fail if a <= x - Insn.ldx_len(), - Insn.jmp(.jgt, .x, 1, 0), - Insn.ret(.{ .k = 13 }), - // a = 4 * (0x7f & 0xf) - // x = 4 * ([4] & 0xf) - // fail if a != x - Insn.ld_imm(4 * (0x7f & 0xf)), - Insn.ldx_msh(4), - Insn.jmp(.jeq, .x, 1, 0), - Insn.ret(.{ .k = 14 }), - // ld #(u32)-1 - // ldx #2 - // add #1 - // fail if a != 0 - Insn.ld_imm(0xffffffff), - Insn.ldx_imm(2), - Insn.alu(.add, .{ .k = 1 }), - Insn.jmp(.jeq, .{ .k = 0 }, 1, 0), - Insn.ret(.{ .k = 15 }), - // sub #1 - // fail if a != (u32)-1 - Insn.alu(.sub, .{ .k = 1 }), - Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0), - Insn.ret(.{ .k = 16 }), - // add x - // fail if a != 1 - Insn.alu(.add, .x), - Insn.jmp(.jeq, .{ .k = 1 }, 1, 0), - Insn.ret(.{ .k = 17 }), - // sub x - // fail if a != (u32)-1 - Insn.alu(.sub, .x), - Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0), - Insn.ret(.{ .k = 18 }), - // ld #16 - // mul #2 - // fail if a != 32 - Insn.ld_imm(16), - Insn.alu(.mul, .{ .k = 2 }), - Insn.jmp(.jeq, .{ .k = 32 }, 1, 0), - Insn.ret(.{ .k = 19 }), - // mul x - // fail if a != 64 - Insn.alu(.mul, .x), - Insn.jmp(.jeq, .{ .k = 64 }, 1, 0), - Insn.ret(.{ .k = 20 }), - // div #2 - // fail if a != 32 - Insn.alu(.div, .{ .k = 2 }), - Insn.jmp(.jeq, .{ .k = 32 }, 1, 0), - Insn.ret(.{ .k = 21 }), - // div x - // fail if a != 16 - Insn.alu(.div, .x), - Insn.jmp(.jeq, .{ .k = 16 }, 1, 0), - Insn.ret(.{ .k = 22 }), - // or #4 - // fail if a != 20 - Insn.alu(.@"or", .{ .k = 4 }), - Insn.jmp(.jeq, .{ .k = 20 }, 1, 0), - Insn.ret(.{ .k = 23 }), - // or x - // fail if a != 22 - Insn.alu(.@"or", .x), - Insn.jmp(.jeq, .{ .k = 22 }, 1, 0), - Insn.ret(.{ .k = 24 }), - // and #6 - // fail if a != 6 - Insn.alu(.@"and", .{ .k = 0b110 }), - Insn.jmp(.jeq, .{ .k = 6 }, 1, 0), - Insn.ret(.{ .k = 25 }), - // and x - // fail if a != 2 - Insn.alu(.@"and", .x), - Insn.jmp(.jeq, .x, 1, 0), - Insn.ret(.{ .k = 26 }), - // xor #15 - // fail if a != 13 - Insn.alu(.xor, .{ .k = 0b1111 }), - Insn.jmp(.jeq, .{ .k = 0b1101 }, 1, 0), - Insn.ret(.{ .k = 27 }), - // xor x - // fail if a != 15 - Insn.alu(.xor, .x), - Insn.jmp(.jeq, .{ .k = 0b1111 }, 1, 0), - Insn.ret(.{ .k = 28 }), - // rsh #1 - // fail if a != 7 - Insn.alu(.rsh, .{ .k = 1 }), - Insn.jmp(.jeq, .{ .k = 0b0111 }, 1, 0), - Insn.ret(.{ .k = 29 }), - // rsh x - // fail if a != 1 - Insn.alu(.rsh, .x), - Insn.jmp(.jeq, .{ .k = 0b0001 }, 1, 0), - Insn.ret(.{ .k = 30 }), - // lsh #1 - // fail if a != 2 - Insn.alu(.lsh, .{ .k = 1 }), - Insn.jmp(.jeq, .{ .k = 0b0010 }, 1, 0), - Insn.ret(.{ .k = 31 }), - // lsh x - // fail if a != 8 - Insn.alu(.lsh, .x), - Insn.jmp(.jeq, .{ .k = 0b1000 }, 1, 0), - Insn.ret(.{ .k = 32 }), - // mod 6 - // fail if a != 2 - Insn.alu(.mod, .{ .k = 6 }), - Insn.jmp(.jeq, .{ .k = 2 }, 1, 0), - Insn.ret(.{ .k = 33 }), - // mod x - // fail if a != 0 - Insn.alu(.mod, .x), - Insn.jmp(.jeq, .{ .k = 0 }, 1, 0), - Insn.ret(.{ .k = 34 }), - // tax - // neg - // fail if a != (u32)-2 - Insn.txa(), - Insn.alu_neg(), - Insn.jmp(.jeq, .{ .k = ~@as(u32, 2) + 1 }, 1, 0), - Insn.ret(.{ .k = 35 }), - // ja #1 (skip the next instruction) - Insn.jmp_ja(1), - Insn.ret(.{ .k = 36 }), - // ld #20 - // tax - // fail if a != 20 - // fail if a != x - Insn.ld_imm(20), - Insn.tax(), - Insn.jmp(.jeq, .{ .k = 20 }, 1, 0), - Insn.ret(.{ .k = 37 }), - Insn.jmp(.jeq, .x, 1, 0), - Insn.ret(.{ .k = 38 }), - // ld #19 - // fail if a == 20 - // fail if a == x - // fail if a >= 20 - // fail if a >= X - Insn.ld_imm(19), - Insn.jmp(.jeq, .{ .k = 20 }, 0, 1), - Insn.ret(.{ .k = 39 }), - Insn.jmp(.jeq, .x, 0, 1), - Insn.ret(.{ .k = 40 }), - Insn.jmp(.jgt, .{ .k = 20 }, 0, 1), - Insn.ret(.{ .k = 41 }), - Insn.jmp(.jgt, .x, 0, 1), - Insn.ret(.{ .k = 42 }), - // ld #21 - // fail if a < 20 - // fail if a < x - Insn.ld_imm(21), - Insn.jmp(.jgt, .{ .k = 20 }, 1, 0), - Insn.ret(.{ .k = 43 }), - Insn.jmp(.jgt, .x, 1, 0), - Insn.ret(.{ .k = 44 }), - // ldx #22 - // fail if a < 22 - // fail if a < x - Insn.ldx_imm(22), - Insn.jmp(.jge, .{ .k = 22 }, 0, 1), - Insn.ret(.{ .k = 45 }), - Insn.jmp(.jge, .x, 0, 1), - Insn.ret(.{ .k = 46 }), - // ld #23 - // fail if a >= 22 - // fail if a >= x - Insn.ld_imm(23), - Insn.jmp(.jge, .{ .k = 22 }, 1, 0), - Insn.ret(.{ .k = 47 }), - Insn.jmp(.jge, .x, 1, 0), - Insn.ret(.{ .k = 48 }), - // ldx #0b10100 - // fail if a & 0b10100 == 0 - // fail if a & x == 0 - Insn.ldx_imm(0b10100), - Insn.jmp(.jset, .{ .k = 0b10100 }, 1, 0), - Insn.ret(.{ .k = 47 }), - Insn.jmp(.jset, .x, 1, 0), - Insn.ret(.{ .k = 48 }), - // ldx #0 - // fail if a & 0 > 0 - // fail if a & x > 0 - Insn.ldx_imm(0), - Insn.jmp(.jset, .{ .k = 0 }, 0, 1), - Insn.ret(.{ .k = 49 }), - Insn.jmp(.jset, .x, 0, 1), - Insn.ret(.{ .k = 50 }), - Insn.ret(.{ .k = 0 }), - }); - try expectPass(&some_data, &.{ - Insn.ld_imm(35), - Insn.ld_imm(0), - Insn.ret(.a), - }); - - // Errors - try expectFail(error.NoReturn, &some_data, &.{ - Insn.ld_imm(10), - }); - try expectFail(error.InvalidOpcode, &some_data, &.{ - Insn.stmt(0x7f, 0xdeadbeef), - }); - try expectFail(error.InvalidOffset, &some_data, &.{ - Insn.stmt(LD | ABS | W, 10), - }); - try expectFail(error.InvalidLocation, &some_data, &.{ - Insn.jmp(.jeq, .{ .k = 0 }, 10, 0), - }); - try expectFail(error.InvalidLocation, &some_data, &.{ - Insn.jmp(.jeq, .{ .k = 0 }, 0, 10), - }); -} diff --git a/lib/std/x/net/ip.zig b/lib/std/x/net/ip.zig deleted file mode 100644 index b3da9725d8..0000000000 --- a/lib/std/x/net/ip.zig +++ /dev/null @@ -1,57 +0,0 @@ -const std = @import("../../std.zig"); - -const fmt = std.fmt; - -const IPv4 = std.x.os.IPv4; -const IPv6 = std.x.os.IPv6; -const Socket = std.x.os.Socket; - -/// A generic IP abstraction. -const ip = @This(); - -/// A union of all eligible types of IP addresses. -pub const Address = union(enum) { - ipv4: IPv4.Address, - ipv6: IPv6.Address, - - /// Instantiate a new address with a IPv4 host and port. - pub fn initIPv4(host: IPv4, port: u16) Address { - return .{ .ipv4 = .{ .host = host, .port = port } }; - } - - /// Instantiate a new address with a IPv6 host and port. - pub fn initIPv6(host: IPv6, port: u16) Address { - return .{ .ipv6 = .{ .host = host, .port = port } }; - } - - /// Re-interpret a generic socket address into an IP address. - pub fn from(address: Socket.Address) ip.Address { - return switch (address) { - .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address }, - .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address }, - }; - } - - /// Re-interpret an IP address into a generic socket address. - pub fn into(self: ip.Address) Socket.Address { - return switch (self) { - .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address }, - .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address }, - }; - } - - /// Implements the `std.fmt.format` API. - pub fn format( - self: ip.Address, - comptime layout: []const u8, - opts: fmt.FormatOptions, - writer: anytype, - ) !void { - if (layout.len != 0) std.fmt.invalidFmtError(layout, self); - _ = opts; - switch (self) { - .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), - .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), - } - } -}; diff --git a/lib/std/x/net/tcp.zig b/lib/std/x/net/tcp.zig deleted file mode 100644 index 0293deb9db..0000000000 --- a/lib/std/x/net/tcp.zig +++ /dev/null @@ -1,447 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); - -const io = std.io; -const os = std.os; -const ip = std.x.net.ip; - -const fmt = std.fmt; -const mem = std.mem; -const testing = std.testing; -const native_os = builtin.os; - -const IPv4 = std.x.os.IPv4; -const IPv6 = std.x.os.IPv6; -const Socket = std.x.os.Socket; -const Buffer = std.x.os.Buffer; - -/// A generic TCP socket abstraction. -const tcp = @This(); - -/// A TCP client-address pair. -pub const Connection = struct { - client: tcp.Client, - address: ip.Address, - - /// Enclose a TCP client and address into a client-address pair. - pub fn from(conn: Socket.Connection) tcp.Connection { - return .{ - .client = tcp.Client.from(conn.socket), - .address = ip.Address.from(conn.address), - }; - } - - /// Unravel a TCP client-address pair into a socket-address pair. - pub fn into(self: tcp.Connection) Socket.Connection { - return .{ - .socket = self.client.socket, - .address = self.address.into(), - }; - } - - /// Closes the underlying client of the connection. - pub fn deinit(self: tcp.Connection) void { - self.client.deinit(); - } -}; - -/// Possible domains that a TCP client/listener may operate over. -pub const Domain = enum(u16) { - ip = os.AF.INET, - ipv6 = os.AF.INET6, -}; - -/// A TCP client. -pub const Client = struct { - socket: Socket, - - /// Implements `std.io.Reader`. - pub const Reader = struct { - client: Client, - flags: u32, - - /// Implements `readFn` for `std.io.Reader`. - pub fn read(self: Client.Reader, buffer: []u8) !usize { - return self.client.read(buffer, self.flags); - } - }; - - /// Implements `std.io.Writer`. - pub const Writer = struct { - client: Client, - flags: u32, - - /// Implements `writeFn` for `std.io.Writer`. - pub fn write(self: Client.Writer, buffer: []const u8) !usize { - return self.client.write(buffer, self.flags); - } - }; - - /// Opens a new client. - pub fn init(domain: tcp.Domain, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Client { - return Client{ - .socket = try Socket.init( - @enumToInt(domain), - os.SOCK.STREAM, - os.IPPROTO.TCP, - flags, - ), - }; - } - - /// Enclose a TCP client over an existing socket. - pub fn from(socket: Socket) Client { - return Client{ .socket = socket }; - } - - /// Closes the client. - pub fn deinit(self: Client) void { - self.socket.deinit(); - } - - /// Shutdown either the read side, write side, or all sides of the client's underlying socket. - pub fn shutdown(self: Client, how: os.ShutdownHow) !void { - return self.socket.shutdown(how); - } - - /// Have the client attempt to the connect to an address. - pub fn connect(self: Client, address: ip.Address) !void { - return self.socket.connect(address.into()); - } - - /// Extracts the error set of a function. - /// TODO: remove after Socket.{read, write} error unions are well-defined across different platforms - fn ErrorSetOf(comptime Function: anytype) type { - return @typeInfo(@typeInfo(@TypeOf(Function)).Fn.return_type.?).ErrorUnion.error_set; - } - - /// Wrap `tcp.Client` into `std.io.Reader`. - pub fn reader(self: Client, flags: u32) io.Reader(Client.Reader, ErrorSetOf(Client.Reader.read), Client.Reader.read) { - return .{ .context = .{ .client = self, .flags = flags } }; - } - - /// Wrap `tcp.Client` into `std.io.Writer`. - pub fn writer(self: Client, flags: u32) io.Writer(Client.Writer, ErrorSetOf(Client.Writer.write), Client.Writer.write) { - return .{ .context = .{ .client = self, .flags = flags } }; - } - - /// Read data from the socket into the buffer provided with a set of flags - /// specified. It returns the number of bytes read into the buffer provided. - pub fn read(self: Client, buf: []u8, flags: u32) !usize { - return self.socket.read(buf, flags); - } - - /// Write a buffer of data provided to the socket with a set of flags specified. - /// It returns the number of bytes that are written to the socket. - pub fn write(self: Client, buf: []const u8, flags: u32) !usize { - return self.socket.write(buf, flags); - } - - /// Writes multiple I/O vectors with a prepended message header to the socket - /// with a set of flags specified. It returns the number of bytes that are - /// written to the socket. - pub fn writeMessage(self: Client, msg: Socket.Message, flags: u32) !usize { - return self.socket.writeMessage(msg, flags); - } - - /// Read multiple I/O vectors with a prepended message header from the socket - /// with a set of flags specified. It returns the number of bytes that were - /// read into the buffer provided. - pub fn readMessage(self: Client, msg: *Socket.Message, flags: u32) !usize { - return self.socket.readMessage(msg, flags); - } - - /// Query and return the latest cached error on the client's underlying socket. - pub fn getError(self: Client) !void { - return self.socket.getError(); - } - - /// Query the read buffer size of the client's underlying socket. - pub fn getReadBufferSize(self: Client) !u32 { - return self.socket.getReadBufferSize(); - } - - /// Query the write buffer size of the client's underlying socket. - pub fn getWriteBufferSize(self: Client) !u32 { - return self.socket.getWriteBufferSize(); - } - - /// Query the address that the client's socket is locally bounded to. - pub fn getLocalAddress(self: Client) !ip.Address { - return ip.Address.from(try self.socket.getLocalAddress()); - } - - /// Query the address that the socket is connected to. - pub fn getRemoteAddress(self: Client) !ip.Address { - return ip.Address.from(try self.socket.getRemoteAddress()); - } - - /// Have close() or shutdown() syscalls block until all queued messages in the client have been successfully - /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption` - /// if the host does not support the option for a socket to linger around up until a timeout specified in - /// seconds. - pub fn setLinger(self: Client, timeout_seconds: ?u16) !void { - return self.socket.setLinger(timeout_seconds); - } - - /// Have keep-alive messages be sent periodically. The timing in which keep-alive messages are sent are - /// dependant on operating system settings. It returns `error.UnsupportedSocketOption` if the host does - /// not support periodically sending keep-alive messages on connection-oriented sockets. - pub fn setKeepAlive(self: Client, enabled: bool) !void { - return self.socket.setKeepAlive(enabled); - } - - /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if - /// the host does not support sockets disabling Nagle's algorithm. - pub fn setNoDelay(self: Client, enabled: bool) !void { - if (@hasDecl(os.TCP, "NODELAY")) { - const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled))); - return self.socket.setOption(os.IPPROTO.TCP, os.TCP.NODELAY, bytes); - } - return error.UnsupportedSocketOption; - } - - /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns - /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. - pub fn setQuickACK(self: Client, enabled: bool) !void { - if (@hasDecl(os.TCP, "QUICKACK")) { - return self.socket.setOption(os.IPPROTO.TCP, os.TCP.QUICKACK, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; - } - - /// Set the write buffer size of the socket. - pub fn setWriteBufferSize(self: Client, size: u32) !void { - return self.socket.setWriteBufferSize(size); - } - - /// Set the read buffer size of the socket. - pub fn setReadBufferSize(self: Client, size: u32) !void { - return self.socket.setReadBufferSize(size); - } - - /// Set a timeout on the socket that is to occur if no messages are successfully written - /// to its bound destination after a specified number of milliseconds. A subsequent write - /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. - pub fn setWriteTimeout(self: Client, milliseconds: u32) !void { - return self.socket.setWriteTimeout(milliseconds); - } - - /// Set a timeout on the socket that is to occur if no messages are successfully read - /// from its bound destination after a specified number of milliseconds. A subsequent - /// read from the socket will thereafter return `error.WouldBlock` should the timeout be - /// exceeded. - pub fn setReadTimeout(self: Client, milliseconds: u32) !void { - return self.socket.setReadTimeout(milliseconds); - } -}; - -/// A TCP listener. -pub const Listener = struct { - socket: Socket, - - /// Opens a new listener. - pub fn init(domain: tcp.Domain, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Listener { - return Listener{ - .socket = try Socket.init( - @enumToInt(domain), - os.SOCK.STREAM, - os.IPPROTO.TCP, - flags, - ), - }; - } - - /// Closes the listener. - pub fn deinit(self: Listener) void { - self.socket.deinit(); - } - - /// Shuts down the underlying listener's socket. The next subsequent call, or - /// a current pending call to accept() after shutdown is called will return - /// an error. - pub fn shutdown(self: Listener) !void { - return self.socket.shutdown(.recv); - } - - /// Binds the listener's socket to an address. - pub fn bind(self: Listener, address: ip.Address) !void { - return self.socket.bind(address.into()); - } - - /// Start listening for incoming connections. - pub fn listen(self: Listener, max_backlog_size: u31) !void { - return self.socket.listen(max_backlog_size); - } - - /// Accept a pending incoming connection queued to the kernel backlog - /// of the listener's socket. - pub fn accept(self: Listener, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !tcp.Connection { - return tcp.Connection.from(try self.socket.accept(flags)); - } - - /// Query and return the latest cached error on the listener's underlying socket. - pub fn getError(self: Client) !void { - return self.socket.getError(); - } - - /// Query the address that the listener's socket is locally bounded to. - pub fn getLocalAddress(self: Listener) !ip.Address { - return ip.Address.from(try self.socket.getLocalAddress()); - } - - /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if - /// the host does not support sockets listening the same address. - pub fn setReuseAddress(self: Listener, enabled: bool) !void { - return self.socket.setReuseAddress(enabled); - } - - /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if - /// the host does not supports sockets listening on the same port. - pub fn setReusePort(self: Listener, enabled: bool) !void { - return self.socket.setReusePort(enabled); - } - - /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not - /// support TCP Fast Open. - pub fn setFastOpen(self: Listener, enabled: bool) !void { - if (@hasDecl(os.TCP, "FASTOPEN")) { - return self.socket.setOption(os.IPPROTO.TCP, os.TCP.FASTOPEN, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; - } - - /// Set a timeout on the listener that is to occur if no new incoming connections come in - /// after a specified number of milliseconds. A subsequent accept call to the listener - /// will thereafter return `error.WouldBlock` should the timeout be exceeded. - pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void { - return self.socket.setReadTimeout(milliseconds); - } -}; - -test "tcp: create client/listener pair" { - if (native_os.tag == .wasi) return error.SkipZigTest; - - const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true }); - defer listener.deinit(); - - try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); - try listener.listen(128); - - var binded_address = try listener.getLocalAddress(); - switch (binded_address) { - .ipv4 => |*ipv4| ipv4.host = IPv4.localhost, - .ipv6 => |*ipv6| ipv6.host = IPv6.localhost, - } - - const client = try tcp.Client.init(.ip, .{ .close_on_exec = true }); - defer client.deinit(); - - try client.connect(binded_address); - - const conn = try listener.accept(.{ .close_on_exec = true }); - defer conn.deinit(); -} - -test "tcp/client: 1ms read timeout" { - if (native_os.tag == .wasi) return error.SkipZigTest; - - const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true }); - defer listener.deinit(); - - try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); - try listener.listen(128); - - var binded_address = try listener.getLocalAddress(); - switch (binded_address) { - .ipv4 => |*ipv4| ipv4.host = IPv4.localhost, - .ipv6 => |*ipv6| ipv6.host = IPv6.localhost, - } - - const client = try tcp.Client.init(.ip, .{ .close_on_exec = true }); - defer client.deinit(); - - try client.connect(binded_address); - try client.setReadTimeout(1); - - const conn = try listener.accept(.{ .close_on_exec = true }); - defer conn.deinit(); - - var buf: [1]u8 = undefined; - try testing.expectError(error.WouldBlock, client.reader(0).read(&buf)); -} - -test "tcp/client: read and write multiple vectors" { - if (native_os.tag == .wasi) return error.SkipZigTest; - - if (builtin.os.tag == .windows) { - // https://github.com/ziglang/zig/issues/13893 - return error.SkipZigTest; - } - - const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true }); - defer listener.deinit(); - - try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); - try listener.listen(128); - - var binded_address = try listener.getLocalAddress(); - switch (binded_address) { - .ipv4 => |*ipv4| ipv4.host = IPv4.localhost, - .ipv6 => |*ipv6| ipv6.host = IPv6.localhost, - } - - const client = try tcp.Client.init(.ip, .{ .close_on_exec = true }); - defer client.deinit(); - - try client.connect(binded_address); - - const conn = try listener.accept(.{ .close_on_exec = true }); - defer conn.deinit(); - - const message = "hello world"; - _ = try conn.client.writeMessage(Socket.Message.fromBuffers(&[_]Buffer{ - Buffer.from(message[0 .. message.len / 2]), - Buffer.from(message[message.len / 2 ..]), - }), 0); - - var buf: [message.len + 1]u8 = undefined; - var msg = Socket.Message.fromBuffers(&[_]Buffer{ - Buffer.from(buf[0 .. message.len / 2]), - Buffer.from(buf[message.len / 2 ..]), - }); - _ = try client.readMessage(&msg, 0); - - try testing.expectEqualStrings(message, buf[0..message.len]); -} - -test "tcp/listener: bind to unspecified ipv4 address" { - if (native_os.tag == .wasi) return error.SkipZigTest; - - const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true }); - defer listener.deinit(); - - try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); - try listener.listen(128); - - const address = try listener.getLocalAddress(); - try testing.expect(address == .ipv4); -} - -test "tcp/listener: bind to unspecified ipv6 address" { - if (native_os.tag == .wasi) return error.SkipZigTest; - - if (builtin.os.tag == .windows) { - // https://github.com/ziglang/zig/issues/13893 - return error.SkipZigTest; - } - - const listener = try tcp.Listener.init(.ipv6, .{ .close_on_exec = true }); - defer listener.deinit(); - - try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0)); - try listener.listen(128); - - const address = try listener.getLocalAddress(); - try testing.expect(address == .ipv6); -} diff --git a/lib/std/x/os/io.zig b/lib/std/x/os/io.zig deleted file mode 100644 index 6c4763df65..0000000000 --- a/lib/std/x/os/io.zig +++ /dev/null @@ -1,224 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); - -const os = std.os; -const mem = std.mem; -const testing = std.testing; -const native_os = builtin.os; -const linux = std.os.linux; - -/// POSIX `iovec`, or Windows `WSABUF`. The difference between the two are the ordering -/// of fields, alongside the length being represented as either a ULONG or a size_t. -pub const Buffer = if (native_os.tag == .windows) - extern struct { - len: c_ulong, - ptr: usize, - - pub fn from(slice: []const u8) Buffer { - return .{ .len = @intCast(c_ulong, slice.len), .ptr = @ptrToInt(slice.ptr) }; - } - - pub fn into(self: Buffer) []const u8 { - return @intToPtr([*]const u8, self.ptr)[0..self.len]; - } - - pub fn intoMutable(self: Buffer) []u8 { - return @intToPtr([*]u8, self.ptr)[0..self.len]; - } - } -else - extern struct { - ptr: usize, - len: usize, - - pub fn from(slice: []const u8) Buffer { - return .{ .ptr = @ptrToInt(slice.ptr), .len = slice.len }; - } - - pub fn into(self: Buffer) []const u8 { - return @intToPtr([*]const u8, self.ptr)[0..self.len]; - } - - pub fn intoMutable(self: Buffer) []u8 { - return @intToPtr([*]u8, self.ptr)[0..self.len]; - } - }; - -pub const Reactor = struct { - pub const InitFlags = enum { - close_on_exec, - }; - - pub const Event = struct { - data: usize, - is_error: bool, - is_hup: bool, - is_readable: bool, - is_writable: bool, - }; - - pub const Interest = struct { - hup: bool = false, - oneshot: bool = false, - readable: bool = false, - writable: bool = false, - }; - - fd: os.fd_t, - - pub fn init(flags: std.enums.EnumFieldStruct(Reactor.InitFlags, bool, false)) !Reactor { - var raw_flags: u32 = 0; - const set = std.EnumSet(Reactor.InitFlags).init(flags); - if (set.contains(.close_on_exec)) raw_flags |= linux.EPOLL.CLOEXEC; - return Reactor{ .fd = try os.epoll_create1(raw_flags) }; - } - - pub fn deinit(self: Reactor) void { - os.close(self.fd); - } - - pub fn update(self: Reactor, fd: os.fd_t, identifier: usize, interest: Reactor.Interest) !void { - var flags: u32 = 0; - flags |= if (interest.oneshot) linux.EPOLL.ONESHOT else linux.EPOLL.ET; - if (interest.hup) flags |= linux.EPOLL.RDHUP; - if (interest.readable) flags |= linux.EPOLL.IN; - if (interest.writable) flags |= linux.EPOLL.OUT; - - const event = &linux.epoll_event{ - .events = flags, - .data = .{ .ptr = identifier }, - }; - - os.epoll_ctl(self.fd, linux.EPOLL.CTL_MOD, fd, event) catch |err| switch (err) { - error.FileDescriptorNotRegistered => try os.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, fd, event), - else => return err, - }; - } - - pub fn remove(self: Reactor, fd: os.fd_t) !void { - // directly from man epoll_ctl BUGS section - // In kernel versions before 2.6.9, the EPOLL_CTL_DEL operation re‐ - // quired a non-null pointer in event, even though this argument is - // ignored. Since Linux 2.6.9, event can be specified as NULL when - // using EPOLL_CTL_DEL. Applications that need to be portable to - // kernels before 2.6.9 should specify a non-null pointer in event. - var event = linux.epoll_event{ - .events = 0, - .data = .{ .ptr = 0 }, - }; - - return os.epoll_ctl(self.fd, linux.EPOLL.CTL_DEL, fd, &event); - } - - pub fn poll(self: Reactor, comptime max_num_events: comptime_int, closure: anytype, timeout_milliseconds: ?u64) !void { - var events: [max_num_events]linux.epoll_event = undefined; - - const num_events = os.epoll_wait(self.fd, &events, if (timeout_milliseconds) |ms| @intCast(i32, ms) else -1); - for (events[0..num_events]) |ev| { - const is_error = ev.events & linux.EPOLL.ERR != 0; - const is_hup = ev.events & (linux.EPOLL.HUP | linux.EPOLL.RDHUP) != 0; - const is_readable = ev.events & linux.EPOLL.IN != 0; - const is_writable = ev.events & linux.EPOLL.OUT != 0; - - try closure.call(Reactor.Event{ - .data = ev.data.ptr, - .is_error = is_error, - .is_hup = is_hup, - .is_readable = is_readable, - .is_writable = is_writable, - }); - } - } -}; - -test "reactor/linux: drive async tcp client/listener pair" { - if (native_os.tag != .linux) return error.SkipZigTest; - - const ip = std.x.net.ip; - const tcp = std.x.net.tcp; - - const IPv4 = std.x.os.IPv4; - const IPv6 = std.x.os.IPv6; - - const reactor = try Reactor.init(.{ .close_on_exec = true }); - defer reactor.deinit(); - - const listener = try tcp.Listener.init(.ip, .{ - .close_on_exec = true, - .nonblocking = true, - }); - defer listener.deinit(); - - try reactor.update(listener.socket.fd, 0, .{ .readable = true }); - try reactor.poll(1, struct { - fn call(event: Reactor.Event) !void { - try testing.expectEqual(Reactor.Event{ - .data = 0, - .is_error = false, - .is_hup = true, - .is_readable = false, - .is_writable = false, - }, event); - } - }, null); - - try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); - try listener.listen(128); - - var binded_address = try listener.getLocalAddress(); - switch (binded_address) { - .ipv4 => |*ipv4| ipv4.host = IPv4.localhost, - .ipv6 => |*ipv6| ipv6.host = IPv6.localhost, - } - - const client = try tcp.Client.init(.ip, .{ - .close_on_exec = true, - .nonblocking = true, - }); - defer client.deinit(); - - try reactor.update(client.socket.fd, 1, .{ .readable = true, .writable = true }); - try reactor.poll(1, struct { - fn call(event: Reactor.Event) !void { - try testing.expectEqual(Reactor.Event{ - .data = 1, - .is_error = false, - .is_hup = true, - .is_readable = false, - .is_writable = true, - }, event); - } - }, null); - - client.connect(binded_address) catch |err| switch (err) { - error.WouldBlock => {}, - else => return err, - }; - - try reactor.poll(1, struct { - fn call(event: Reactor.Event) !void { - try testing.expectEqual(Reactor.Event{ - .data = 1, - .is_error = false, - .is_hup = false, - .is_readable = false, - .is_writable = true, - }, event); - } - }, null); - - try reactor.poll(1, struct { - fn call(event: Reactor.Event) !void { - try testing.expectEqual(Reactor.Event{ - .data = 0, - .is_error = false, - .is_hup = false, - .is_readable = true, - .is_writable = false, - }, event); - } - }, null); - - try reactor.remove(client.socket.fd); - try reactor.remove(listener.socket.fd); -} diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig deleted file mode 100644 index e00299e243..0000000000 --- a/lib/std/x/os/net.zig +++ /dev/null @@ -1,605 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); - -const os = std.os; -const fmt = std.fmt; -const mem = std.mem; -const math = std.math; -const testing = std.testing; -const native_os = builtin.os; -const have_ifnamesize = @hasDecl(os.system, "IFNAMESIZE"); - -pub const ResolveScopeIdError = error{ - NameTooLong, - PermissionDenied, - AddressFamilyNotSupported, - ProtocolFamilyNotAvailable, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - SystemResources, - ProtocolNotSupported, - SocketTypeNotSupported, - InterfaceNotFound, - FileSystem, - Unexpected, -}; - -/// Resolves a network interface name into a scope/zone ID. It returns -/// an error if either resolution fails, or if the interface name is -/// too long. -pub fn resolveScopeId(name: []const u8) ResolveScopeIdError!u32 { - if (have_ifnamesize) { - if (name.len >= os.IFNAMESIZE) return error.NameTooLong; - - if (native_os.tag == .windows or comptime native_os.tag.isDarwin()) { - var interface_name: [os.IFNAMESIZE:0]u8 = undefined; - mem.copy(u8, &interface_name, name); - interface_name[name.len] = 0; - - const rc = blk: { - if (native_os.tag == .windows) { - break :blk os.windows.ws2_32.if_nametoindex(@ptrCast([*:0]const u8, &interface_name)); - } else { - const index = os.system.if_nametoindex(@ptrCast([*:0]const u8, &interface_name)); - break :blk @bitCast(u32, index); - } - }; - if (rc == 0) { - return error.InterfaceNotFound; - } - return rc; - } - - if (native_os.tag == .linux) { - const fd = try os.socket(os.AF.INET, os.SOCK.DGRAM, 0); - defer os.closeSocket(fd); - - var f: os.ifreq = undefined; - mem.copy(u8, &f.ifrn.name, name); - f.ifrn.name[name.len] = 0; - - try os.ioctl_SIOCGIFINDEX(fd, &f); - - return @bitCast(u32, f.ifru.ivalue); - } - } - - return error.InterfaceNotFound; -} - -/// An IPv4 address comprised of 4 bytes. -pub const IPv4 = extern struct { - /// A IPv4 host-port pair. - pub const Address = extern struct { - host: IPv4, - port: u16, - }; - - /// Octets of a IPv4 address designating the local host. - pub const localhost_octets = [_]u8{ 127, 0, 0, 1 }; - - /// The IPv4 address of the local host. - pub const localhost: IPv4 = .{ .octets = localhost_octets }; - - /// Octets of an unspecified IPv4 address. - pub const unspecified_octets = [_]u8{0} ** 4; - - /// An unspecified IPv4 address. - pub const unspecified: IPv4 = .{ .octets = unspecified_octets }; - - /// Octets of a broadcast IPv4 address. - pub const broadcast_octets = [_]u8{255} ** 4; - - /// An IPv4 broadcast address. - pub const broadcast: IPv4 = .{ .octets = broadcast_octets }; - - /// The prefix octet pattern of a link-local IPv4 address. - pub const link_local_prefix = [_]u8{ 169, 254 }; - - /// The prefix octet patterns of IPv4 addresses intended for - /// documentation. - pub const documentation_prefixes = [_][]const u8{ - &[_]u8{ 192, 0, 2 }, - &[_]u8{ 198, 51, 100 }, - &[_]u8{ 203, 0, 113 }, - }; - - octets: [4]u8, - - /// Returns whether or not the two addresses are equal to, less than, or - /// greater than each other. - pub fn cmp(self: IPv4, other: IPv4) math.Order { - return mem.order(u8, &self.octets, &other.octets); - } - - /// Returns true if both addresses are semantically equivalent. - pub fn eql(self: IPv4, other: IPv4) bool { - return mem.eql(u8, &self.octets, &other.octets); - } - - /// Returns true if the address is a loopback address. - pub fn isLoopback(self: IPv4) bool { - return self.octets[0] == 127; - } - - /// Returns true if the address is an unspecified IPv4 address. - pub fn isUnspecified(self: IPv4) bool { - return mem.eql(u8, &self.octets, &unspecified_octets); - } - - /// Returns true if the address is a private IPv4 address. - pub fn isPrivate(self: IPv4) bool { - return self.octets[0] == 10 or - (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or - (self.octets[0] == 192 and self.octets[1] == 168); - } - - /// Returns true if the address is a link-local IPv4 address. - pub fn isLinkLocal(self: IPv4) bool { - return mem.startsWith(u8, &self.octets, &link_local_prefix); - } - - /// Returns true if the address is a multicast IPv4 address. - pub fn isMulticast(self: IPv4) bool { - return self.octets[0] >= 224 and self.octets[0] <= 239; - } - - /// Returns true if the address is a IPv4 broadcast address. - pub fn isBroadcast(self: IPv4) bool { - return mem.eql(u8, &self.octets, &broadcast_octets); - } - - /// Returns true if the address is in a range designated for documentation. Refer - /// to IETF RFC 5737 for more details. - pub fn isDocumentation(self: IPv4) bool { - inline for (documentation_prefixes) |prefix| { - if (mem.startsWith(u8, &self.octets, prefix)) { - return true; - } - } - return false; - } - - /// Implements the `std.fmt.format` API. - pub fn format( - self: IPv4, - comptime layout: []const u8, - opts: fmt.FormatOptions, - writer: anytype, - ) !void { - _ = opts; - if (layout.len != 0) std.fmt.invalidFmtError(layout, self); - - try fmt.format(writer, "{}.{}.{}.{}", .{ - self.octets[0], - self.octets[1], - self.octets[2], - self.octets[3], - }); - } - - /// Set of possible errors that may encountered when parsing an IPv4 - /// address. - pub const ParseError = error{ - UnexpectedEndOfOctet, - TooManyOctets, - OctetOverflow, - UnexpectedToken, - IncompleteAddress, - }; - - /// Parses an arbitrary IPv4 address. - pub fn parse(buf: []const u8) ParseError!IPv4 { - var octets: [4]u8 = undefined; - var octet: u8 = 0; - - var index: u8 = 0; - var saw_any_digits: bool = false; - - for (buf) |c| { - switch (c) { - '.' => { - if (!saw_any_digits) return error.UnexpectedEndOfOctet; - if (index == 3) return error.TooManyOctets; - octets[index] = octet; - index += 1; - octet = 0; - saw_any_digits = false; - }, - '0'...'9' => { - saw_any_digits = true; - octet = math.mul(u8, octet, 10) catch return error.OctetOverflow; - octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow; - }, - else => return error.UnexpectedToken, - } - } - - if (index == 3 and saw_any_digits) { - octets[index] = octet; - return IPv4{ .octets = octets }; - } - - return error.IncompleteAddress; - } - - /// Maps the address to its IPv6 equivalent. In most cases, you would - /// want to map the address to its IPv6 equivalent rather than directly - /// re-interpreting the address. - pub fn mapToIPv6(self: IPv4) IPv6 { - var octets: [16]u8 = undefined; - mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix); - mem.copy(u8, octets[12..], &self.octets); - return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; - } - - /// Directly re-interprets the address to its IPv6 equivalent. In most - /// cases, you would want to map the address to its IPv6 equivalent rather - /// than directly re-interpreting the address. - pub fn toIPv6(self: IPv4) IPv6 { - var octets: [16]u8 = undefined; - mem.set(u8, octets[0..12], 0); - mem.copy(u8, octets[12..], &self.octets); - return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; - } -}; - -/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes -/// for a scope ID; cumulatively summing to 20 bytes in total. -pub const IPv6 = extern struct { - /// A IPv6 host-port pair. - pub const Address = extern struct { - host: IPv6, - port: u16, - }; - - /// Octets of a IPv6 address designating the local host. - pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; - - /// The IPv6 address of the local host. - pub const localhost: IPv6 = .{ - .octets = localhost_octets, - .scope_id = no_scope_id, - }; - - /// Octets of an unspecified IPv6 address. - pub const unspecified_octets = [_]u8{0} ** 16; - - /// An unspecified IPv6 address. - pub const unspecified: IPv6 = .{ - .octets = unspecified_octets, - .scope_id = no_scope_id, - }; - - /// The prefix of a IPv6 address that is mapped to a IPv4 address. - pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2; - - /// A marker value used to designate an IPv6 address with no - /// associated scope ID. - pub const no_scope_id = math.maxInt(u32); - - octets: [16]u8, - scope_id: u32, - - /// Returns whether or not the two addresses are equal to, less than, or - /// greater than each other. - pub fn cmp(self: IPv6, other: IPv6) math.Order { - return switch (mem.order(u8, self.octets, other.octets)) { - .eq => math.order(self.scope_id, other.scope_id), - else => |order| order, - }; - } - - /// Returns true if both addresses are semantically equivalent. - pub fn eql(self: IPv6, other: IPv6) bool { - return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets); - } - - /// Returns true if the address is an unspecified IPv6 address. - pub fn isUnspecified(self: IPv6) bool { - return mem.eql(u8, &self.octets, &unspecified_octets); - } - - /// Returns true if the address is a loopback address. - pub fn isLoopback(self: IPv6) bool { - return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and - mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 }); - } - - /// Returns true if the address maps to an IPv4 address. - pub fn mapsToIPv4(self: IPv6) bool { - return mem.startsWith(u8, &self.octets, &v4_mapped_prefix); - } - - /// Returns an IPv4 address representative of the address should - /// it the address be mapped to an IPv4 address. It returns null - /// otherwise. - pub fn toIPv4(self: IPv6) ?IPv4 { - if (!self.mapsToIPv4()) return null; - return IPv4{ .octets = self.octets[12..][0..4].* }; - } - - /// Returns true if the address is a multicast IPv6 address. - pub fn isMulticast(self: IPv6) bool { - return self.octets[0] == 0xFF; - } - - /// Returns true if the address is a unicast link local IPv6 address. - pub fn isLinkLocal(self: IPv6) bool { - return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80; - } - - /// Returns true if the address is a deprecated unicast site local - /// IPv6 address. Refer to IETF RFC 3879 for more details as to - /// why they are deprecated. - pub fn isSiteLocal(self: IPv6) bool { - return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0; - } - - /// IPv6 multicast address scopes. - pub const Scope = enum(u8) { - interface = 1, - link = 2, - realm = 3, - admin = 4, - site = 5, - organization = 8, - global = 14, - unknown = 0xFF, - }; - - /// Returns the multicast scope of the address. - pub fn scope(self: IPv6) Scope { - if (!self.isMulticast()) return .unknown; - - return switch (self.octets[0] & 0x0F) { - 1 => .interface, - 2 => .link, - 3 => .realm, - 4 => .admin, - 5 => .site, - 8 => .organization, - 14 => .global, - else => .unknown, - }; - } - - /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the - /// address lower-cased octets, while specifying 'X' or 'S' formats the - /// address using upper-cased ASCII octets. - /// - /// The default specifier is 'x'. - pub fn format( - self: IPv6, - comptime layout: []const u8, - opts: fmt.FormatOptions, - writer: anytype, - ) !void { - _ = opts; - const specifier = comptime &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) { - 'x', 'X' => |specifier| specifier, - 's' => 'x', - 'S' => 'X', - else => std.fmt.invalidFmtError(layout, self), - }}; - - if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) { - return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{ - 0xFF, - 0xFF, - self.octets[12], - self.octets[13], - self.octets[14], - self.octets[15], - }); - } - - const zero_span: struct { from: usize, to: usize } = span: { - var i: usize = 0; - while (i < self.octets.len) : (i += 2) { - if (self.octets[i] == 0 and self.octets[i + 1] == 0) break; - } else break :span .{ .from = 0, .to = 0 }; - - const from = i; - - while (i < self.octets.len) : (i += 2) { - if (self.octets[i] != 0 or self.octets[i + 1] != 0) break; - } - - break :span .{ .from = from, .to = i }; - }; - - var i: usize = 0; - while (i != 16) : (i += 2) { - if (zero_span.from != zero_span.to and i == zero_span.from) { - try writer.writeAll("::"); - } else if (i >= zero_span.from and i < zero_span.to) {} else { - if (i != 0 and i != zero_span.to) try writer.writeAll(":"); - - const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1]; - try fmt.formatIntValue(val, specifier, .{}, writer); - } - } - - if (self.scope_id != no_scope_id and self.scope_id != 0) { - try fmt.format(writer, "%{d}", .{self.scope_id}); - } - } - - /// Set of possible errors that may encountered when parsing an IPv6 - /// address. - pub const ParseError = error{ - MalformedV4Mapping, - InterfaceNotFound, - UnknownScopeId, - } || IPv4.ParseError; - - /// Parses an arbitrary IPv6 address, including link-local addresses. - pub fn parse(buf: []const u8) ParseError!IPv6 { - if (mem.lastIndexOfScalar(u8, buf, '%')) |index| { - const ip_slice = buf[0..index]; - const scope_id_slice = buf[index + 1 ..]; - - if (scope_id_slice.len == 0) return error.UnknownScopeId; - - const scope_id: u32 = switch (scope_id_slice[0]) { - '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10), - else => resolveScopeId(scope_id_slice) catch |err| switch (err) { - error.InterfaceNotFound => return error.InterfaceNotFound, - else => err, - }, - } catch return error.UnknownScopeId; - - return parseWithScopeID(ip_slice, scope_id); - } - - return parseWithScopeID(buf, no_scope_id); - } - - /// Parses an IPv6 address with a pre-specified scope ID. Presumes - /// that the address is not a link-local address. - pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 { - var octets: [16]u8 = undefined; - var octet: u16 = 0; - var tail: [16]u8 = undefined; - - var out: []u8 = &octets; - var index: u8 = 0; - - var saw_any_digits: bool = false; - var abbrv: bool = false; - - for (buf) |c, i| { - switch (c) { - ':' => { - if (!saw_any_digits) { - if (abbrv) return error.UnexpectedToken; - if (i != 0) abbrv = true; - mem.set(u8, out[index..], 0); - out = &tail; - index = 0; - continue; - } - if (index == 14) return error.TooManyOctets; - - out[index] = @truncate(u8, octet >> 8); - index += 1; - out[index] = @truncate(u8, octet); - index += 1; - - octet = 0; - saw_any_digits = false; - }, - '.' => { - if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) { - return error.MalformedV4Mapping; - } - const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; - const v4 = try IPv4.parse(buf[start_index..]); - octets[10] = 0xFF; - octets[11] = 0xFF; - mem.copy(u8, octets[12..], &v4.octets); - - return IPv6{ .octets = octets, .scope_id = scope_id }; - }, - else => { - saw_any_digits = true; - const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken; - octet = math.mul(u16, octet, 16) catch return error.OctetOverflow; - octet = math.add(u16, octet, digit) catch return error.OctetOverflow; - }, - } - } - - if (!saw_any_digits and !abbrv) { - return error.IncompleteAddress; - } - - if (index == 14) { - out[14] = @truncate(u8, octet >> 8); - out[15] = @truncate(u8, octet); - } else { - out[index] = @truncate(u8, octet >> 8); - index += 1; - out[index] = @truncate(u8, octet); - index += 1; - mem.copy(u8, octets[16 - index ..], out[0..index]); - } - - return IPv6{ .octets = octets, .scope_id = scope_id }; - } -}; - -test { - testing.refAllDecls(@This()); -} - -test "ip: convert to and from ipv6" { - try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()}); - try testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4()); - - try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()}); - try testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4()); - - try testing.expect(IPv4.localhost.toIPv6().toIPv4() == null); - try testing.expectFmt("127.0.0.1", "{?}", .{IPv4.localhost.mapToIPv6().toIPv4()}); -} - -test "ipv4: parse & format" { - const cases = [_][]const u8{ - "0.0.0.0", - "255.255.255.255", - "1.2.3.4", - "123.255.0.91", - "127.0.0.1", - }; - - for (cases) |case| { - try testing.expectFmt(case, "{}", .{try IPv4.parse(case)}); - } -} - -test "ipv6: parse & format" { - const inputs = [_][]const u8{ - "FF01:0:0:0:0:0:0:FB", - "FF01::Fb", - "::1", - "::", - "2001:db8::", - "::1234:5678", - "2001:db8::1234:5678", - "::ffff:123.5.123.5", - }; - - const outputs = [_][]const u8{ - "ff01::fb", - "ff01::fb", - "::1", - "::", - "2001:db8::", - "::1234:5678", - "2001:db8::1234:5678", - "::ffff:123.5.123.5", - }; - - for (inputs) |input, i| { - try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); - } -} - -test "ipv6: parse & format addresses with scope ids" { - if (!have_ifnamesize) return error.SkipZigTest; - const iface = if (native_os.tag == .linux) - "lo" - else - "lo0"; - const input = "FF01::FB%" ++ iface; - const output = "ff01::fb%1"; - - const parsed = IPv6.parse(input) catch |err| switch (err) { - error.InterfaceNotFound => return, - else => return err, - }; - - try testing.expectFmt(output, "{}", .{parsed}); -} diff --git a/lib/std/x/os/socket.zig b/lib/std/x/os/socket.zig deleted file mode 100644 index 99782710cb..0000000000 --- a/lib/std/x/os/socket.zig +++ /dev/null @@ -1,320 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); -const net = @import("net.zig"); - -const os = std.os; -const fmt = std.fmt; -const mem = std.mem; -const time = std.time; -const meta = std.meta; -const native_os = builtin.os; -const native_endian = builtin.cpu.arch.endian(); - -const Buffer = std.x.os.Buffer; - -const assert = std.debug.assert; - -/// A generic, cross-platform socket abstraction. -pub const Socket = struct { - /// A socket-address pair. - pub const Connection = struct { - socket: Socket, - address: Socket.Address, - - /// Enclose a socket and address into a socket-address pair. - pub fn from(socket: Socket, address: Socket.Address) Socket.Connection { - return .{ .socket = socket, .address = address }; - } - }; - - /// A generic socket address abstraction. It is safe to directly access and modify - /// the fields of a `Socket.Address`. - pub const Address = union(enum) { - pub const Native = struct { - pub const requires_prepended_length = native_os.getVersionRange() == .semver; - pub const Length = if (requires_prepended_length) u8 else [0]u8; - - pub const Family = if (requires_prepended_length) u8 else c_ushort; - - /// POSIX `sockaddr.storage`. The expected size and alignment is specified in IETF RFC 2553. - pub const Storage = extern struct { - pub const expected_size = os.sockaddr.SS_MAXSIZE; - pub const expected_alignment = 8; - - pub const padding_size = expected_size - - mem.alignForward(@sizeOf(Address.Native.Length), expected_alignment) - - mem.alignForward(@sizeOf(Address.Native.Family), expected_alignment); - - len: Address.Native.Length align(expected_alignment) = undefined, - family: Address.Native.Family align(expected_alignment) = undefined, - padding: [padding_size]u8 align(expected_alignment) = undefined, - - comptime { - assert(@sizeOf(Storage) == Storage.expected_size); - assert(@alignOf(Storage) == Storage.expected_alignment); - } - }; - }; - - ipv4: net.IPv4.Address, - ipv6: net.IPv6.Address, - - /// Instantiate a new address with a IPv4 host and port. - pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address { - return .{ .ipv4 = .{ .host = host, .port = port } }; - } - - /// Instantiate a new address with a IPv6 host and port. - pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address { - return .{ .ipv6 = .{ .host = host, .port = port } }; - } - - /// Parses a `sockaddr` into a generic socket address. - pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address { - switch (address.family) { - os.AF.INET => { - const info = @ptrCast(*const os.sockaddr.in, address); - const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) }; - const port = mem.bigToNative(u16, info.port); - return Socket.Address.initIPv4(host, port); - }, - os.AF.INET6 => { - const info = @ptrCast(*const os.sockaddr.in6, address); - const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id }; - const port = mem.bigToNative(u16, info.port); - return Socket.Address.initIPv6(host, port); - }, - else => unreachable, - } - } - - /// Encodes a generic socket address into an extern union that may be reliably - /// casted into a `sockaddr` which may be passed into socket syscalls. - pub fn toNative(self: Socket.Address) extern union { - ipv4: os.sockaddr.in, - ipv6: os.sockaddr.in6, - } { - return switch (self) { - .ipv4 => |address| .{ - .ipv4 = .{ - .addr = @bitCast(u32, address.host.octets), - .port = mem.nativeToBig(u16, address.port), - }, - }, - .ipv6 => |address| .{ - .ipv6 = .{ - .addr = address.host.octets, - .port = mem.nativeToBig(u16, address.port), - .scope_id = address.host.scope_id, - .flowinfo = 0, - }, - }, - }; - } - - /// Returns the number of bytes that make up the `sockaddr` equivalent to the address. - pub fn getNativeSize(self: Socket.Address) u32 { - return switch (self) { - .ipv4 => @sizeOf(os.sockaddr.in), - .ipv6 => @sizeOf(os.sockaddr.in6), - }; - } - - /// Implements the `std.fmt.format` API. - pub fn format( - self: Socket.Address, - comptime layout: []const u8, - opts: fmt.FormatOptions, - writer: anytype, - ) !void { - if (layout.len != 0) std.fmt.invalidFmtError(layout, self); - _ = opts; - switch (self) { - .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), - .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), - } - } - }; - - /// POSIX `msghdr`. Denotes a destination address, set of buffers, control data, and flags. Ported - /// directly from musl. - pub const Message = if (native_os.isAtLeast(.windows, .vista) != null and native_os.isAtLeast(.windows, .vista).?) - extern struct { - name: usize = @ptrToInt(@as(?[*]u8, null)), - name_len: c_int = 0, - - buffers: usize = undefined, - buffers_len: c_ulong = undefined, - - control: Buffer = .{ - .ptr = @ptrToInt(@as(?[*]u8, null)), - .len = 0, - }, - flags: c_ulong = 0, - - pub usingnamespace MessageMixin(Message); - } - else if (native_os.tag == .windows) - extern struct { - name: usize = @ptrToInt(@as(?[*]u8, null)), - name_len: c_int = 0, - - buffers: usize = undefined, - buffers_len: u32 = undefined, - - control: Buffer = .{ - .ptr = @ptrToInt(@as(?[*]u8, null)), - .len = 0, - }, - flags: u32 = 0, - - pub usingnamespace MessageMixin(Message); - } - else if (@sizeOf(usize) > 4 and native_endian == .Big) - extern struct { - name: usize = @ptrToInt(@as(?[*]u8, null)), - name_len: c_uint = 0, - - buffers: usize = undefined, - _pad_1: c_int = 0, - buffers_len: c_int = undefined, - - control: usize = @ptrToInt(@as(?[*]u8, null)), - _pad_2: c_int = 0, - control_len: c_uint = 0, - - flags: c_int = 0, - - pub usingnamespace MessageMixin(Message); - } - else if (@sizeOf(usize) > 4 and native_endian == .Little) - extern struct { - name: usize = @ptrToInt(@as(?[*]u8, null)), - name_len: c_uint = 0, - - buffers: usize = undefined, - buffers_len: c_int = undefined, - _pad_1: c_int = 0, - - control: usize = @ptrToInt(@as(?[*]u8, null)), - control_len: c_uint = 0, - _pad_2: c_int = 0, - - flags: c_int = 0, - - pub usingnamespace MessageMixin(Message); - } - else - extern struct { - name: usize = @ptrToInt(@as(?[*]u8, null)), - name_len: c_uint = 0, - - buffers: usize = undefined, - buffers_len: c_int = undefined, - - control: usize = @ptrToInt(@as(?[*]u8, null)), - control_len: c_uint = 0, - - flags: c_int = 0, - - pub usingnamespace MessageMixin(Message); - }; - - fn MessageMixin(comptime Self: type) type { - return struct { - pub fn fromBuffers(buffers: []const Buffer) Self { - var self: Self = .{}; - self.setBuffers(buffers); - return self; - } - - pub fn setName(self: *Self, name: []const u8) void { - self.name = @ptrToInt(name.ptr); - self.name_len = @intCast(meta.fieldInfo(Self, .name_len).type, name.len); - } - - pub fn setBuffers(self: *Self, buffers: []const Buffer) void { - self.buffers = @ptrToInt(buffers.ptr); - self.buffers_len = @intCast(meta.fieldInfo(Self, .buffers_len).type, buffers.len); - } - - pub fn setControl(self: *Self, control: []const u8) void { - if (native_os.tag == .windows) { - self.control = Buffer.from(control); - } else { - self.control = @ptrToInt(control.ptr); - self.control_len = @intCast(meta.fieldInfo(Self, .control_len).type, control.len); - } - } - - pub fn setFlags(self: *Self, flags: u32) void { - self.flags = @intCast(meta.fieldInfo(Self, .flags).type, flags); - } - - pub fn getName(self: Self) []const u8 { - return @intToPtr([*]const u8, self.name)[0..@intCast(usize, self.name_len)]; - } - - pub fn getBuffers(self: Self) []const Buffer { - return @intToPtr([*]const Buffer, self.buffers)[0..@intCast(usize, self.buffers_len)]; - } - - pub fn getControl(self: Self) []const u8 { - if (native_os.tag == .windows) { - return self.control.into(); - } else { - return @intToPtr([*]const u8, self.control)[0..@intCast(usize, self.control_len)]; - } - } - - pub fn getFlags(self: Self) u32 { - return @intCast(u32, self.flags); - } - }; - } - - /// POSIX `linger`, denoting the linger settings of a socket. - /// - /// Microsoft's documentation and glibc denote the fields to be unsigned - /// short's on Windows, whereas glibc and musl denote the fields to be - /// int's on every other platform. - pub const Linger = extern struct { - pub const Field = switch (native_os.tag) { - .windows => c_ushort, - else => c_int, - }; - - enabled: Field, - timeout_seconds: Field, - - pub fn init(timeout_seconds: ?u16) Socket.Linger { - return .{ - .enabled = @intCast(Socket.Linger.Field, @boolToInt(timeout_seconds != null)), - .timeout_seconds = if (timeout_seconds) |seconds| @intCast(Socket.Linger.Field, seconds) else 0, - }; - } - }; - - /// Possible set of flags to initialize a socket with. - pub const InitFlags = enum { - // Initialize a socket to be non-blocking. - nonblocking, - - // Have a socket close itself on exec syscalls. - close_on_exec, - }; - - /// The underlying handle of a socket. - fd: os.socket_t, - - /// Enclose a socket abstraction over an existing socket file descriptor. - pub fn from(fd: os.socket_t) Socket { - return Socket{ .fd = fd }; - } - - /// Mix in socket syscalls depending on the platform we are compiling against. - pub usingnamespace switch (native_os.tag) { - .windows => @import("socket_windows.zig"), - else => @import("socket_posix.zig"), - }.Mixin(Socket); -}; diff --git a/lib/std/x/os/socket_posix.zig b/lib/std/x/os/socket_posix.zig deleted file mode 100644 index 859075aa20..0000000000 --- a/lib/std/x/os/socket_posix.zig +++ /dev/null @@ -1,275 +0,0 @@ -const std = @import("../../std.zig"); - -const os = std.os; -const mem = std.mem; -const time = std.time; - -pub fn Mixin(comptime Socket: type) type { - return struct { - /// Open a new socket. - pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket { - var raw_flags: u32 = socket_type; - const set = std.EnumSet(Socket.InitFlags).init(flags); - if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC; - if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK; - return Socket{ .fd = try os.socket(domain, raw_flags, protocol) }; - } - - /// Closes the socket. - pub fn deinit(self: Socket) void { - os.closeSocket(self.fd); - } - - /// Shutdown either the read side, write side, or all side of the socket. - pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { - return os.shutdown(self.fd, how); - } - - /// Binds the socket to an address. - pub fn bind(self: Socket, address: Socket.Address) !void { - return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); - } - - /// Start listening for incoming connections on the socket. - pub fn listen(self: Socket, max_backlog_size: u31) !void { - return os.listen(self.fd, max_backlog_size); - } - - /// Have the socket attempt to the connect to an address. - pub fn connect(self: Socket, address: Socket.Address) !void { - return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); - } - - /// Accept a pending incoming connection queued to the kernel backlog - /// of the socket. - pub fn accept(self: Socket, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection { - var address: Socket.Address.Native.Storage = undefined; - var address_len: u32 = @sizeOf(Socket.Address.Native.Storage); - - var raw_flags: u32 = 0; - const set = std.EnumSet(Socket.InitFlags).init(flags); - if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC; - if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK; - - const socket = Socket{ .fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, raw_flags) }; - const socket_address = Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address)); - - return Socket.Connection.from(socket, socket_address); - } - - /// Read data from the socket into the buffer provided with a set of flags - /// specified. It returns the number of bytes read into the buffer provided. - pub fn read(self: Socket, buf: []u8, flags: u32) !usize { - return os.recv(self.fd, buf, flags); - } - - /// Write a buffer of data provided to the socket with a set of flags specified. - /// It returns the number of bytes that are written to the socket. - pub fn write(self: Socket, buf: []const u8, flags: u32) !usize { - return os.send(self.fd, buf, flags); - } - - /// Writes multiple I/O vectors with a prepended message header to the socket - /// with a set of flags specified. It returns the number of bytes that are - /// written to the socket. - pub fn writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize { - while (true) { - const rc = os.system.sendmsg(self.fd, &msg, @intCast(c_int, flags)); - return switch (os.errno(rc)) { - .SUCCESS => return @intCast(usize, rc), - .ACCES => error.AccessDenied, - .AGAIN => error.WouldBlock, - .ALREADY => error.FastOpenAlreadyInProgress, - .BADF => unreachable, // always a race condition - .CONNRESET => error.ConnectionResetByPeer, - .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. - .FAULT => unreachable, // An invalid user space address was specified for an argument. - .INTR => continue, - .INVAL => unreachable, // Invalid argument passed. - .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => error.MessageTooBig, - .NOBUFS => error.SystemResources, - .NOMEM => error.SystemResources, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => error.BrokenPipe, - .AFNOSUPPORT => error.AddressFamilyNotSupported, - .LOOP => error.SymLinkLoop, - .NAMETOOLONG => error.NameTooLong, - .NOENT => error.FileNotFound, - .NOTDIR => error.NotDir, - .HOSTUNREACH => error.NetworkUnreachable, - .NETUNREACH => error.NetworkUnreachable, - .NOTCONN => error.SocketNotConnected, - .NETDOWN => error.NetworkSubsystemFailed, - else => |err| os.unexpectedErrno(err), - }; - } - } - - /// Read multiple I/O vectors with a prepended message header from the socket - /// with a set of flags specified. It returns the number of bytes that were - /// read into the buffer provided. - pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize { - while (true) { - const rc = os.system.recvmsg(self.fd, msg, @intCast(c_int, flags)); - return switch (os.errno(rc)) { - .SUCCESS => @intCast(usize, rc), - .BADF => unreachable, // always a race condition - .FAULT => unreachable, - .INVAL => unreachable, - .NOTCONN => unreachable, - .NOTSOCK => unreachable, - .INTR => continue, - .AGAIN => error.WouldBlock, - .NOMEM => error.SystemResources, - .CONNREFUSED => error.ConnectionRefused, - .CONNRESET => error.ConnectionResetByPeer, - else => |err| os.unexpectedErrno(err), - }; - } - } - - /// Query the address that the socket is locally bounded to. - pub fn getLocalAddress(self: Socket) !Socket.Address { - var address: Socket.Address.Native.Storage = undefined; - var address_len: u32 = @sizeOf(Socket.Address.Native.Storage); - try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len); - return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address)); - } - - /// Query the address that the socket is connected to. - pub fn getRemoteAddress(self: Socket) !Socket.Address { - var address: Socket.Address.Native.Storage = undefined; - var address_len: u32 = @sizeOf(Socket.Address.Native.Storage); - try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len); - return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address)); - } - - /// Query and return the latest cached error on the socket. - pub fn getError(self: Socket) !void { - return os.getsockoptError(self.fd); - } - - /// Query the read buffer size of the socket. - pub fn getReadBufferSize(self: Socket) !u32 { - var value: u32 = undefined; - var value_len: u32 = @sizeOf(u32); - - const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&value), &value_len); - return switch (os.errno(rc)) { - .SUCCESS => value, - .BADF => error.BadFileDescriptor, - .FAULT => error.InvalidAddressSpace, - .INVAL => error.InvalidSocketOption, - .NOPROTOOPT => error.UnknownSocketOption, - .NOTSOCK => error.NotASocket, - else => |err| os.unexpectedErrno(err), - }; - } - - /// Query the write buffer size of the socket. - pub fn getWriteBufferSize(self: Socket) !u32 { - var value: u32 = undefined; - var value_len: u32 = @sizeOf(u32); - - const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&value), &value_len); - return switch (os.errno(rc)) { - .SUCCESS => value, - .BADF => error.BadFileDescriptor, - .FAULT => error.InvalidAddressSpace, - .INVAL => error.InvalidSocketOption, - .NOPROTOOPT => error.UnknownSocketOption, - .NOTSOCK => error.NotASocket, - else => |err| os.unexpectedErrno(err), - }; - } - - /// Set a socket option. - pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void { - return os.setsockopt(self.fd, level, code, value); - } - - /// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully - /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption` - /// if the host does not support the option for a socket to linger around up until a timeout specified in - /// seconds. - pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void { - if (@hasDecl(os.SO, "LINGER")) { - const settings = Socket.Linger.init(timeout_seconds); - return self.setOption(os.SOL.SOCKET, os.SO.LINGER, mem.asBytes(&settings)); - } - - return error.UnsupportedSocketOption; - } - - /// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive - /// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if - /// the host does not support periodically sending keep-alive messages on connection-oriented sockets. - pub fn setKeepAlive(self: Socket, enabled: bool) !void { - if (@hasDecl(os.SO, "KEEPALIVE")) { - return self.setOption(os.SOL.SOCKET, os.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; - } - - /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if - /// the host does not support sockets listening the same address. - pub fn setReuseAddress(self: Socket, enabled: bool) !void { - if (@hasDecl(os.SO, "REUSEADDR")) { - return self.setOption(os.SOL.SOCKET, os.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; - } - - /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if - /// the host does not supports sockets listening on the same port. - pub fn setReusePort(self: Socket, enabled: bool) !void { - if (@hasDecl(os.SO, "REUSEPORT")) { - return self.setOption(os.SOL.SOCKET, os.SO.REUSEPORT, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - return error.UnsupportedSocketOption; - } - - /// Set the write buffer size of the socket. - pub fn setWriteBufferSize(self: Socket, size: u32) !void { - return self.setOption(os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&size)); - } - - /// Set the read buffer size of the socket. - pub fn setReadBufferSize(self: Socket, size: u32) !void { - return self.setOption(os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&size)); - } - - /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is - /// set on a non-blocking socket. - /// - /// Set a timeout on the socket that is to occur if no messages are successfully written - /// to its bound destination after a specified number of milliseconds. A subsequent write - /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. - pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { - const timeout = os.timeval{ - .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), - .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), - }; - - return self.setOption(os.SOL.SOCKET, os.SO.SNDTIMEO, mem.asBytes(&timeout)); - } - - /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is - /// set on a non-blocking socket. - /// - /// Set a timeout on the socket that is to occur if no messages are successfully read - /// from its bound destination after a specified number of milliseconds. A subsequent - /// read from the socket will thereafter return `error.WouldBlock` should the timeout be - /// exceeded. - pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { - const timeout = os.timeval{ - .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), - .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), - }; - - return self.setOption(os.SOL.SOCKET, os.SO.RCVTIMEO, mem.asBytes(&timeout)); - } - }; -} diff --git a/lib/std/x/os/socket_windows.zig b/lib/std/x/os/socket_windows.zig deleted file mode 100644 index 43b047dd10..0000000000 --- a/lib/std/x/os/socket_windows.zig +++ /dev/null @@ -1,458 +0,0 @@ -const std = @import("../../std.zig"); -const net = @import("net.zig"); - -const os = std.os; -const mem = std.mem; - -const windows = std.os.windows; -const ws2_32 = windows.ws2_32; - -pub fn Mixin(comptime Socket: type) type { - return struct { - /// Open a new socket. - pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket { - var raw_flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED; - const set = std.EnumSet(Socket.InitFlags).init(flags); - if (set.contains(.close_on_exec)) raw_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; - - const fd = ws2_32.WSASocketW( - @intCast(i32, domain), - @intCast(i32, socket_type), - @intCast(i32, protocol), - null, - 0, - raw_flags, - ); - if (fd == ws2_32.INVALID_SOCKET) { - return switch (ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => { - _ = try windows.WSAStartup(2, 2); - return init(domain, socket_type, protocol, flags); - }, - .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported, - .WSAEMFILE => error.ProcessFdQuotaExceeded, - .WSAENOBUFS => error.SystemResources, - .WSAEPROTONOSUPPORT => error.ProtocolNotSupported, - else => |err| windows.unexpectedWSAError(err), - }; - } - - if (set.contains(.nonblocking)) { - var enabled: c_ulong = 1; - const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled); - if (rc == ws2_32.SOCKET_ERROR) { - return windows.unexpectedWSAError(ws2_32.WSAGetLastError()); - } - } - - return Socket{ .fd = fd }; - } - - /// Closes the socket. - pub fn deinit(self: Socket) void { - _ = ws2_32.closesocket(self.fd); - } - - /// Shutdown either the read side, write side, or all side of the socket. - pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { - const rc = ws2_32.shutdown(self.fd, switch (how) { - .recv => ws2_32.SD_RECEIVE, - .send => ws2_32.SD_SEND, - .both => ws2_32.SD_BOTH, - }); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAECONNABORTED => return error.ConnectionAborted, - .WSAECONNRESET => return error.ConnectionResetByPeer, - .WSAEINPROGRESS => return error.BlockingOperationInProgress, - .WSAEINVAL => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAENOTCONN => return error.SocketNotConnected, - .WSAENOTSOCK => unreachable, - .WSANOTINITIALISED => unreachable, - else => |err| return windows.unexpectedWSAError(err), - }; - } - } - - /// Binds the socket to an address. - pub fn bind(self: Socket, address: Socket.Address) !void { - const rc = ws2_32.bind(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize())); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAEACCES => error.AccessDenied, - .WSAEADDRINUSE => error.AddressInUse, - .WSAEADDRNOTAVAIL => error.AddressNotAvailable, - .WSAEFAULT => error.BadAddress, - .WSAEINPROGRESS => error.WouldBlock, - .WSAEINVAL => error.AlreadyBound, - .WSAENOBUFS => error.NoEphemeralPortsAvailable, - .WSAENOTSOCK => error.NotASocket, - else => |err| windows.unexpectedWSAError(err), - }; - } - } - - /// Start listening for incoming connections on the socket. - pub fn listen(self: Socket, max_backlog_size: u31) !void { - const rc = ws2_32.listen(self.fd, max_backlog_size); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAEADDRINUSE => error.AddressInUse, - .WSAEISCONN => error.AlreadyConnected, - .WSAEINVAL => error.SocketNotBound, - .WSAEMFILE, .WSAENOBUFS => error.SystemResources, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAEINPROGRESS => error.WouldBlock, - else => |err| windows.unexpectedWSAError(err), - }; - } - } - - /// Have the socket attempt to the connect to an address. - pub fn connect(self: Socket, address: Socket.Address) !void { - const rc = ws2_32.connect(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize())); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAEADDRINUSE => error.AddressInUse, - .WSAEADDRNOTAVAIL => error.AddressNotAvailable, - .WSAECONNREFUSED => error.ConnectionRefused, - .WSAETIMEDOUT => error.ConnectionTimedOut, - .WSAEFAULT => error.BadAddress, - .WSAEINVAL => error.ListeningSocket, - .WSAEISCONN => error.AlreadyConnected, - .WSAENOTSOCK => error.NotASocket, - .WSAEACCES => error.BroadcastNotEnabled, - .WSAENOBUFS => error.SystemResources, - .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported, - .WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock, - .WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable, - else => |err| windows.unexpectedWSAError(err), - }; - } - } - - /// Accept a pending incoming connection queued to the kernel backlog - /// of the socket. - pub fn accept(self: Socket, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection { - var address: Socket.Address.Native.Storage = undefined; - var address_len: c_int = @sizeOf(Socket.Address.Native.Storage); - - const fd = ws2_32.accept(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len); - if (fd == ws2_32.INVALID_SOCKET) { - return switch (ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAECONNRESET => error.ConnectionResetByPeer, - .WSAEFAULT => unreachable, - .WSAEINVAL => error.SocketNotListening, - .WSAEMFILE => error.ProcessFdQuotaExceeded, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENOBUFS => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAEWOULDBLOCK => error.WouldBlock, - else => |err| windows.unexpectedWSAError(err), - }; - } - - const socket = Socket.from(fd); - errdefer socket.deinit(); - - const socket_address = Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address)); - - const set = std.EnumSet(Socket.InitFlags).init(flags); - if (set.contains(.nonblocking)) { - var enabled: c_ulong = 1; - const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled); - if (rc == ws2_32.SOCKET_ERROR) { - return windows.unexpectedWSAError(ws2_32.WSAGetLastError()); - } - } - - return Socket.Connection.from(socket, socket_address); - } - - /// Read data from the socket into the buffer provided with a set of flags - /// specified. It returns the number of bytes read into the buffer provided. - pub fn read(self: Socket, buf: []u8, flags: u32) !usize { - var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }}; - var num_bytes: u32 = undefined; - var flags_ = flags; - - const rc = ws2_32.WSARecv(self.fd, bufs, 1, &num_bytes, &flags_, null, null); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAECONNABORTED => error.ConnectionAborted, - .WSAECONNRESET => error.ConnectionResetByPeer, - .WSAEDISCON => error.ConnectionClosedByPeer, - .WSAEFAULT => error.BadBuffer, - .WSAEINPROGRESS, - .WSAEWOULDBLOCK, - .WSA_IO_PENDING, - .WSAETIMEDOUT, - => error.WouldBlock, - .WSAEINTR => error.Cancelled, - .WSAEINVAL => error.SocketNotBound, - .WSAEMSGSIZE => error.MessageTooLarge, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENETRESET => error.NetworkReset, - .WSAENOTCONN => error.SocketNotConnected, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAESHUTDOWN => error.AlreadyShutdown, - .WSA_OPERATION_ABORTED => error.OperationAborted, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return @intCast(usize, num_bytes); - } - - /// Write a buffer of data provided to the socket with a set of flags specified. - /// It returns the number of bytes that are written to the socket. - pub fn write(self: Socket, buf: []const u8, flags: u32) !usize { - var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = @intToPtr([*]u8, @ptrToInt(buf.ptr)) }}; - var num_bytes: u32 = undefined; - - const rc = ws2_32.WSASend(self.fd, bufs, 1, &num_bytes, flags, null, null); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAECONNABORTED => error.ConnectionAborted, - .WSAECONNRESET => error.ConnectionResetByPeer, - .WSAEFAULT => error.BadBuffer, - .WSAEINPROGRESS, - .WSAEWOULDBLOCK, - .WSA_IO_PENDING, - .WSAETIMEDOUT, - => error.WouldBlock, - .WSAEINTR => error.Cancelled, - .WSAEINVAL => error.SocketNotBound, - .WSAEMSGSIZE => error.MessageTooLarge, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENETRESET => error.NetworkReset, - .WSAENOBUFS => error.BufferDeadlock, - .WSAENOTCONN => error.SocketNotConnected, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAESHUTDOWN => error.AlreadyShutdown, - .WSA_OPERATION_ABORTED => error.OperationAborted, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return @intCast(usize, num_bytes); - } - - /// Writes multiple I/O vectors with a prepended message header to the socket - /// with a set of flags specified. It returns the number of bytes that are - /// written to the socket. - pub fn writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize { - const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSASENDMSG, self.fd, ws2_32.WSAID_WSASENDMSG); - - var num_bytes: u32 = undefined; - - const rc = call(self.fd, &msg, flags, &num_bytes, null, null); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAECONNABORTED => error.ConnectionAborted, - .WSAECONNRESET => error.ConnectionResetByPeer, - .WSAEFAULT => error.BadBuffer, - .WSAEINPROGRESS, - .WSAEWOULDBLOCK, - .WSA_IO_PENDING, - .WSAETIMEDOUT, - => error.WouldBlock, - .WSAEINTR => error.Cancelled, - .WSAEINVAL => error.SocketNotBound, - .WSAEMSGSIZE => error.MessageTooLarge, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENETRESET => error.NetworkReset, - .WSAENOBUFS => error.BufferDeadlock, - .WSAENOTCONN => error.SocketNotConnected, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAESHUTDOWN => error.AlreadyShutdown, - .WSA_OPERATION_ABORTED => error.OperationAborted, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return @intCast(usize, num_bytes); - } - - /// Read multiple I/O vectors with a prepended message header from the socket - /// with a set of flags specified. It returns the number of bytes that were - /// read into the buffer provided. - pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize { - _ = flags; - const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSARECVMSG, self.fd, ws2_32.WSAID_WSARECVMSG); - - var num_bytes: u32 = undefined; - - const rc = call(self.fd, msg, &num_bytes, null, null); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSAECONNABORTED => error.ConnectionAborted, - .WSAECONNRESET => error.ConnectionResetByPeer, - .WSAEDISCON => error.ConnectionClosedByPeer, - .WSAEFAULT => error.BadBuffer, - .WSAEINPROGRESS, - .WSAEWOULDBLOCK, - .WSA_IO_PENDING, - .WSAETIMEDOUT, - => error.WouldBlock, - .WSAEINTR => error.Cancelled, - .WSAEINVAL => error.SocketNotBound, - .WSAEMSGSIZE => error.MessageTooLarge, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENETRESET => error.NetworkReset, - .WSAENOTCONN => error.SocketNotConnected, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEOPNOTSUPP => error.OperationNotSupported, - .WSAESHUTDOWN => error.AlreadyShutdown, - .WSA_OPERATION_ABORTED => error.OperationAborted, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return @intCast(usize, num_bytes); - } - - /// Query the address that the socket is locally bounded to. - pub fn getLocalAddress(self: Socket) !Socket.Address { - var address: Socket.Address.Native.Storage = undefined; - var address_len: c_int = @sizeOf(Socket.Address.Native.Storage); - - const rc = ws2_32.getsockname(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAEFAULT => unreachable, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEINVAL => error.SocketNotBound, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address)); - } - - /// Query the address that the socket is connected to. - pub fn getRemoteAddress(self: Socket) !Socket.Address { - var address: Socket.Address.Native.Storage = undefined; - var address_len: c_int = @sizeOf(Socket.Address.Native.Storage); - - const rc = ws2_32.getpeername(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAEFAULT => unreachable, - .WSAENETDOWN => error.NetworkSubsystemFailed, - .WSAENOTSOCK => error.FileDescriptorNotASocket, - .WSAEINVAL => error.SocketNotBound, - else => |err| windows.unexpectedWSAError(err), - }; - } - - return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address)); - } - - /// Query and return the latest cached error on the socket. - pub fn getError(self: Socket) !void { - _ = self; - return {}; - } - - /// Query the read buffer size of the socket. - pub fn getReadBufferSize(self: Socket) !u32 { - _ = self; - return 0; - } - - /// Query the write buffer size of the socket. - pub fn getWriteBufferSize(self: Socket) !u32 { - _ = self; - return 0; - } - - /// Set a socket option. - pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void { - const rc = ws2_32.setsockopt(self.fd, @intCast(i32, level), @intCast(i32, code), value.ptr, @intCast(i32, value.len)); - if (rc == ws2_32.SOCKET_ERROR) { - return switch (ws2_32.WSAGetLastError()) { - .WSANOTINITIALISED => unreachable, - .WSAENETDOWN => return error.NetworkSubsystemFailed, - .WSAEFAULT => unreachable, - .WSAENOTSOCK => return error.FileDescriptorNotASocket, - .WSAEINVAL => return error.SocketNotBound, - else => |err| windows.unexpectedWSAError(err), - }; - } - } - - /// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully - /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption` - /// if the host does not support the option for a socket to linger around up until a timeout specified in - /// seconds. - pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void { - const settings = Socket.Linger.init(timeout_seconds); - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.LINGER, mem.asBytes(&settings)); - } - - /// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive - /// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if - /// the host does not support periodically sending keep-alive messages on connection-oriented sockets. - pub fn setKeepAlive(self: Socket, enabled: bool) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - - /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if - /// the host does not support sockets listening the same address. - pub fn setReuseAddress(self: Socket, enabled: bool) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - } - - /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if - /// the host does not supports sockets listening on the same port. - /// - /// TODO: verify if this truly mimicks SO.REUSEPORT behavior, or if SO.REUSE_UNICASTPORT provides the correct behavior - pub fn setReusePort(self: Socket, enabled: bool) !void { - try self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.BROADCAST, mem.asBytes(&@as(u32, @boolToInt(enabled)))); - try self.setReuseAddress(enabled); - } - - /// Set the write buffer size of the socket. - pub fn setWriteBufferSize(self: Socket, size: u32) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDBUF, mem.asBytes(&size)); - } - - /// Set the read buffer size of the socket. - pub fn setReadBufferSize(self: Socket, size: u32) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVBUF, mem.asBytes(&size)); - } - - /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is - /// set on a non-blocking socket. - /// - /// Set a timeout on the socket that is to occur if no messages are successfully written - /// to its bound destination after a specified number of milliseconds. A subsequent write - /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. - pub fn setWriteTimeout(self: Socket, milliseconds: u32) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDTIMEO, mem.asBytes(&milliseconds)); - } - - /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is - /// set on a non-blocking socket. - /// - /// Set a timeout on the socket that is to occur if no messages are successfully read - /// from its bound destination after a specified number of milliseconds. A subsequent - /// read from the socket will thereafter return `error.WouldBlock` should the timeout be - /// exceeded. - pub fn setReadTimeout(self: Socket, milliseconds: u32) !void { - return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVTIMEO, mem.asBytes(&milliseconds)); - } - }; -}