mirror of
https://github.com/ziglang/zig.git
synced 2026-02-14 13:30:45 +00:00
remove the experimental std.x namespace
Playtime is over. I'm working on networking now.
This commit is contained in:
parent
ebcfc86bb9
commit
cd0d514643
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
const std = @import("std.zig");
|
||||
|
||||
pub const os = struct {
|
||||
pub const Socket = @import("x/os/socket.zig").Socket;
|
||||
pub usingnamespace @import("x/os/io.zig");
|
||||
pub usingnamespace @import("x/os/net.zig");
|
||||
};
|
||||
|
||||
pub const net = struct {
|
||||
pub const ip = @import("x/net/ip.zig");
|
||||
pub const tcp = @import("x/net/tcp.zig");
|
||||
pub const bpf = @import("x/net/bpf.zig");
|
||||
};
|
||||
|
||||
test {
|
||||
inline for (.{ os, net }) |module| {
|
||||
std.testing.refAllDecls(module);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 }),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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});
|
||||
}
|
||||
@ -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);
|
||||
};
|
||||
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user