feature. See also
. The project being documented here (as the example) is the Zig library itself.
File
Code
const builtin = @import("builtin");
const native_os = builtin.os.tag;
const std = @import("../std.zig");
const Io = std.Io;
const assert = std.debug.assert;
pub const HostName = @import("net/HostName.zig");
pub const Protocol = enum(u32) {
hopopts = 0,
icmp = 1,
igmp = 2,
ipip = 4,
tcp = 6,
egp = 8,
pup = 12,
udp = 17,
idp = 22,
tp = 29,
dccp = 33,
ipv6 = 41,
routing = 43,
fragment = 44,
rsvp = 46,
gre = 47,
esp = 50,
ah = 51,
icmpv6 = 58,
none = 59,
dstopts = 60,
mtp = 92,
beetph = 94,
encap = 98,
pim = 103,
comp = 108,
sctp = 132,
mh = 135,
udplite = 136,
mpls = 137,
ethernet = 143,
raw = 255,
mptcp = 262,
_,
};
pub const has_unix_sockets = switch (native_os) {
.windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
.wasi => false,
else => true,
};
pub const default_kernel_backlog = 128;
pub const IpAddress = union(enum) {
ip4: Ip4Address,
ip6: Ip6Address,
pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
pub const ParseLiteralError = error{ InvalidAddress, InvalidPort };
pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
if (text.len == 0) return error.InvalidAddress;
if (text[0] == '[') {
const addr_end = std.mem.findScalar(u8, text, ']') orelse
return error.InvalidAddress;
const addr_text = text[1..addr_end];
const port: u16 = p: {
if (addr_end == text.len - 1) break :p 0;
if (text[addr_end + 1] != ':') return error.InvalidAddress;
break :p std.fmt.parseInt(u16, text[addr_end + 2 ..], 10) catch return error.InvalidPort;
};
return parseIp6(addr_text, port) catch error.InvalidAddress;
}
if (std.mem.findScalar(u8, text, ':')) |i| {
const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
return .{ .ip4 = .{
.bytes = addr.bytes,
.port = std.fmt.parseInt(u16, text[i + 1 ..], 10) catch return error.InvalidPort,
} };
}
return parseIp4(text, 0) catch error.InvalidAddress;
}
pub fn parse(text: []const u8, port: u16) !IpAddress {
if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.NonCanonical,
=> {},
}
return parseIp6(text, port);
}
pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
return .{ .ip4 = try Ip4Address.parse(text, port) };
}
pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
return .{ .ip6 = try Ip6Address.parse(text, port) };
}
pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress {
if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.NonCanonical,
=> {},
}
return resolveIp6(io, text, port);
}
pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress {
return .{ .ip6 = try Ip6Address.resolve(io, text, port) };
}
pub fn getPort(a: IpAddress) u16 {
return switch (a) {
inline .ip4, .ip6 => |x| x.port,
};
}
pub fn setPort(a: *IpAddress, port: u16) void {
switch (a.*) {
.ip4 => a.ip4.port = port,
.ip6 => a.ip6.port = port,
}
}
pub fn fromIp6(ip6: Ip6Address) IpAddress {
return if (Ip4Address.fromIp6(ip6)) |ip4| .{ .ip4 = ip4 } else .{ .ip6 = ip6 };
}
pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void {
switch (a) {
.ip4 => |x| return x.format(w),
.ip6 => |x| return x.formatResolved(io, w),
}
}
pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
switch (a) {
inline .ip4, .ip6 => |x| return x.format(w),
}
}
pub fn eql(a: *const IpAddress, b: *const IpAddress) bool {
return switch (a.*) {
.ip4 => |a_ip4| switch (b.*) {
.ip4 => |b_ip4| a_ip4.eql(b_ip4),
else => false,
},
.ip6 => |a_ip6| switch (b.*) {
.ip6 => |b_ip6| a_ip6.eql(b_ip6),
else => false,
},
};
}
pub const ListenError = error{
AddressInUse,
AddressUnavailable,
NetworkDown,
SystemResources,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
AddressFamilyUnsupported,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
OptionUnsupported,
} || Io.UnexpectedError || Io.Cancelable;
pub const ListenOptions = struct {
kernel_backlog: u31 = default_kernel_backlog,
reuse_address: bool = false,
mode: Socket.Mode = .stream,
protocol: Protocol = .tcp,
};
pub fn listen(address: *const IpAddress, io: Io, options: ListenOptions) ListenError!Server {
return .{
.socket = try io.vtable.netListenIp(io.userdata, address, options),
.options = if (Server.AcceptOptions != void) .{
.mode = options.mode,
.protocol = options.protocol,
},
};
}
pub const BindError = error{
AddressInUse,
AddressUnavailable,
AddressFamilyUnsupported,
SystemResources,
NetworkDown,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
SocketModeUnsupported,
OptionUnsupported,
} || Io.UnexpectedError || Io.Cancelable;
pub const BindOptions = struct {
ip6_only: ?bool = null,
allow_broadcast: bool = false,
mode: Socket.Mode,
protocol: ?Protocol = null,
};
pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
return io.vtable.netBindIp(io.userdata, address, options);
}
pub const ConnectError = error{
AddressUnavailable,
AddressFamilyUnsupported,
SystemResources,
ConnectionPending,
ConnectionRefused,
ConnectionResetByPeer,
HostUnreachable,
NetworkUnreachable,
Timeout,
OptionUnsupported,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
AccessDenied,
WouldBlock,
NetworkDown,
} || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
pub const ConnectOptions = struct {
mode: Socket.Mode,
protocol: ?Protocol = null,
timeout: Io.Timeout = .none,
};
pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
return .{ .socket = try io.vtable.netConnectIp(io.userdata, address, options) };
}
};
pub const Ip4Address = struct {
bytes: [4]u8,
port: u16,
pub fn loopback(port: u16) Ip4Address {
return .{
.bytes = .{ 127, 0, 0, 1 },
.port = port,
};
}
pub fn unspecified(port: u16) Ip4Address {
return .{
.bytes = .{ 0, 0, 0, 0 },
.port = port,
};
}
pub fn fromIp6(ip6: Ip6Address) ?Ip4Address {
return if (std.mem.eql(u8, ip6.bytes[0..12], &.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) .{
.bytes = ip6.bytes[12..].*,
.port = ip6.port,
} else null;
}
pub fn fromAny(addr: IpAddress) ?Ip6Address {
return switch (addr) {
.ip4 => |ip4| ip4,
.ip6 => |ip6| fromIp6(ip6),
};
}
pub const ParseError = error{
Overflow,
InvalidEnd,
InvalidCharacter,
Incomplete,
NonCanonical,
};
pub fn parse(buffer: []const u8, port: u16) ParseError!Ip4Address {
var bytes: [4]u8 = @splat(0);
var index: u8 = 0;
var saw_any_digits = false;
var has_zero_prefix = false;
for (buffer) |c| switch (c) {
'.' => {
if (!saw_any_digits) return error.InvalidCharacter;
if (index == 3) return error.InvalidEnd;
index += 1;
saw_any_digits = false;
has_zero_prefix = false;
},
'0'...'9' => {
if (c == '0' and !saw_any_digits) {
has_zero_prefix = true;
} else if (has_zero_prefix) {
return error.NonCanonical;
}
saw_any_digits = true;
bytes[index] = try std.math.mul(u8, bytes[index], 10);
bytes[index] = try std.math.add(u8, bytes[index], c - '0');
},
else => return error.InvalidCharacter,
};
if (index == 3 and saw_any_digits) return .{
.bytes = bytes,
.port = port,
};
return error.Incomplete;
}
pub fn format(a: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &a.bytes;
try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });
}
pub fn eql(a: Ip4Address, b: Ip4Address) bool {
const a_int: u32 = @bitCast(a.bytes);
const b_int: u32 = @bitCast(b.bytes);
return a.port == b.port and a_int == b_int;
}
};
pub const Ip6Address = struct {
port: u16,
bytes: [16]u8,
flow: u32 = 0,
interface: Interface = .none,
pub const Policy = struct {
addr: [16]u8,
len: u8,
mask: u8,
prec: u8,
label: u8,
};
pub fn loopback(port: u16) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
.port = port,
};
}
pub fn unspecified(port: u16) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.port = port,
};
}
pub fn fromIp4(ip4: Ip4Address) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff } ++ ip4.bytes,
.port = ip4.port,
};
}
pub fn fromAny(addr: IpAddress) Ip6Address {
return switch (addr) {
.ip4 => |ip4| fromIp4(ip4),
.ip6 => |ip6| ip6,
};
}
pub const Unresolved = struct {
bytes: [16]u8,
interface_name: ?[]const u8,
pub const Parsed = union(enum) {
success: Unresolved,
invalid_byte: usize,
incomplete,
junk_after_end: usize,
interface_name_oversized: usize,
invalid_ip4_mapping: usize,
overflow: usize,
};
pub fn parse(text: []const u8) Parsed {
if (text.len < 2) return .incomplete;
const ip4_prefix = "::ffff:";
if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
return .{ .invalid_ip4_mapping = ip4_prefix.len };
const b = parsed.bytes;
return .{ .success = .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
.interface_name = null,
} };
}
var parts: [8]u16 = @splat(0);
var parts_i: u8 = 0;
var text_i: u8 = 0;
var digit_i: u8 = 0;
var compress_start: ?u8 = null;
var interface_name_text: ?[]const u8 = null;
const State = union(enum) { digit, end };
state: switch (State.digit) {
.digit => c: switch (text[text_i]) {
'a'...'f' => |c| {
const digit = c - 'a' + 10;
parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + digit;
if (digit_i == 4) return .{ .invalid_byte = text_i };
digit_i += 1;
text_i += 1;
if (text.len - text_i == 0) {
parts_i += 1;
continue :state .end;
}
continue :c text[text_i];
},
'A'...'F' => |c| continue :c c - 'A' + 'a',
'0'...'9' => |c| {
const digit = c - '0';
parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + digit;
if (digit_i == 4) return .{ .invalid_byte = text_i };
digit_i += 1;
text_i += 1;
if (text.len - text_i == 0) {
parts_i += 1;
continue :state .end;
}
continue :c text[text_i];
},
':' => {
if (digit_i == 0) {
if (compress_start != null) return .{ .invalid_byte = text_i };
if (text_i == 0) {
text_i += 1;
if (text[text_i] != ':') return .{ .invalid_byte = text_i };
assert(parts_i == 0);
}
compress_start = parts_i;
text_i += 1;
if (text.len - text_i == 0) continue :state .end;
continue :c text[text_i];
} else {
parts_i += 1;
if (parts.len - parts_i == 0) continue :state .end;
digit_i = 0;
text_i += 1;
if (text.len - text_i == 0) return .incomplete;
continue :c text[text_i];
}
},
'%' => {
if (digit_i == 0) return .{ .invalid_byte = text_i };
parts_i += 1;
text_i += 1;
const name = text[text_i..];
if (name.len == 0) return .incomplete;
interface_name_text = name;
text_i = std.math.cast(u8, text.len) orelse return .{ .overflow = text.len };
continue :state .end;
},
else => return .{ .invalid_byte = text_i },
},
.end => {
if (text.len - text_i != 0) return .{ .junk_after_end = text_i };
const remaining = parts.len - parts_i;
if (compress_start) |s| {
const src = parts[s..parts_i];
@memmove(parts[parts.len - src.len ..], src);
@memset(parts[s..][0..remaining], 0);
} else {
if (remaining != 0) return .incomplete;
}
for (&parts) |*part| part.* = @byteSwap(part.*);
return .{ .success = .{
.bytes = @bitCast(parts),
.interface_name = interface_name_text,
} };
},
}
}
pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &u.bytes;
if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
} else {
const parts: [8]u16 = .{
std.mem.readInt(u16, bytes[0..2], .big),
std.mem.readInt(u16, bytes[2..4], .big),
std.mem.readInt(u16, bytes[4..6], .big),
std.mem.readInt(u16, bytes[6..8], .big),
std.mem.readInt(u16, bytes[8..10], .big),
std.mem.readInt(u16, bytes[10..12], .big),
std.mem.readInt(u16, bytes[12..14], .big),
std.mem.readInt(u16, bytes[14..16], .big),
};
var longest_start: usize = 8;
var longest_len: usize = 0;
var current_start: usize = 0;
var current_len: usize = 0;
for (parts, 0..) |part, i| {
if (part == 0) {
if (current_len == 0) {
current_start = i;
}
current_len += 1;
if (current_len > longest_len) {
longest_start = current_start;
longest_len = current_len;
}
} else {
current_len = 0;
}
}
if (longest_len < 2) {
longest_start = 8;
longest_len = 0;
}
var i: usize = 0;
while (parts.len - i != 0) : (i += 1) {
if (i == longest_start) {
try w.writeAll(if (i == 0) "::" else ":");
i += longest_len - 1;
continue;
}
try w.print("{x}", .{parts[i]});
if (i != parts.len - 1) {
try w.writeAll(":");
}
}
}
if (u.interface_name) |n| try w.print("%{s}", .{n});
}
};
pub const ParseError = error{
ParseFailed,
UnresolvedScope,
};
pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
switch (Unresolved.parse(buffer)) {
.success => |p| return .{
.bytes = p.bytes,
.port = port,
.interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
},
else => return error.ParseFailed,
}
return .{ .ip6 = try Ip6Address.parse(buffer, port) };
}
pub const ResolveError = error{
ParseFailed,
NameTooLong,
} || Interface.Name.ResolveError;
pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
return switch (Unresolved.parse(buffer)) {
.success => |p| return .{
.bytes = p.bytes,
.port = port,
.interface = i: {
const text = p.interface_name orelse break :i .none;
const name: Interface.Name = try .fromSlice(text);
break :i try name.resolve(io);
},
},
else => return error.ParseFailed,
};
}
pub const FormatError = Io.Writer.Error || Interface.NameError;
pub fn formatResolved(a: *const Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
const interface_name = if (a.interface.isNone()) null else try a.interface.name(io);
const u: Unresolved = .{
.bytes = a.bytes,
.interface_name = if (interface_name) |name| name.toSlice() else null,
};
try w.print("[{f}]:{d}", .{ u, a.port });
}
pub fn format(a: *const Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
const u: Unresolved = .{ .bytes = a.bytes, .interface_name = null };
try w.print("[{f}]:{d}", .{ u, a.port });
}
pub fn eql(a: Ip6Address, b: Ip6Address) bool {
return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);
}
pub fn isMultiCast(a: Ip6Address) bool {
return a.bytes[0] == 0xff;
}
pub fn isLinkLocal(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0xfe and (b[1] & 0xc0) == 0x80;
}
pub fn isLoopBack(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0 and b[1] == 0 and
b[2] == 0 and
b[12] == 0 and b[13] == 0 and
b[14] == 0 and b[15] == 1;
}
pub fn isSiteLocal(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0;
}
pub fn policy(a: Ip6Address) *const Policy {
const b = &a.bytes;
for (&defined_policies) |*p| {
if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue;
if ((b[p.len] & p.mask) != p.addr[p.len]) continue;
return p;
}
unreachable;
}
pub fn scope(a: Ip6Address) u8 {
if (isMultiCast(a)) return a.bytes[1] & 15;
if (isLinkLocal(a)) return 2;
if (isLoopBack(a)) return 2;
if (isSiteLocal(a)) return 5;
return 14;
}
const defined_policies = [_]Policy{
.{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
.len = 15,
.mask = 0xff,
.prec = 50,
.label = 0,
},
.{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
.len = 11,
.mask = 0xff,
.prec = 35,
.label = 4,
},
.{
.addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
.len = 1,
.mask = 0xff,
.prec = 30,
.label = 2,
},
.{
.addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
.len = 3,
.mask = 0xff,
.prec = 5,
.label = 5,
},
.{
.addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
.len = 0,
.mask = 0xfe,
.prec = 3,
.label = 13,
},
// pool, so despite the RFC, treating them as special
// is probably wrong.
// { "", 11, 0xff, 1, 3 },
// { "\xfe\xc0", 1, 0xc0, 1, 11 },
// { "\x3f\xfe", 1, 0xff, 1, 12 },
// Last rule must match all addresses to stop loop.
.{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
.len = 0,
.mask = 0,
.prec = 40,
.label = 1,
},
};
};
pub const UnixAddress = struct {
path: []const u8,
pub const max_len = switch (native_os) {
.windows => std.os.windows.PATH_MAX_WIDE,
else => 108,
};
pub const InitError = error{NameTooLong};
pub fn init(p: []const u8) InitError!UnixAddress {
if (p.len > max_len) return error.NameTooLong;
return .{ .path = p };
}
pub fn isAbstract(ua: *const UnixAddress) bool {
return ua.path.len == 0 or ua.path[0] == 0;
}
pub const ListenError = error{
AddressFamilyUnsupported,
AddressInUse,
NetworkDown,
SystemResources,
SymLinkLoop,
FileNotFound,
NotDir,
ReadOnlyFileSystem,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
AccessDenied,
PermissionDenied,
AddressUnavailable,
} || Io.Cancelable || Io.UnexpectedError;
pub const ListenOptions = struct {
kernel_backlog: u31 = default_kernel_backlog,
};
pub fn listen(ua: *const UnixAddress, io: Io, options: ListenOptions) ListenError!Server {
assert(ua.path.len <= max_len);
return .{
.socket = .{
.handle = try io.vtable.netListenUnix(io.userdata, ua, options),
.address = .{ .ip4 = .loopback(0) },
},
.options = if (Server.AcceptOptions != void) .{ .mode = .stream, .protocol = null },
};
}
pub const ConnectError = error{
SystemResources,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
AddressFamilyUnsupported,
ProtocolUnsupportedBySystem,
SocketModeUnsupported,
AccessDenied,
PermissionDenied,
SymLinkLoop,
FileNotFound,
NotDir,
ReadOnlyFileSystem,
WouldBlock,
NetworkDown,
ConnectionRefused,
} || Io.Cancelable || Io.UnexpectedError;
pub fn connect(ua: *const UnixAddress, io: Io) ConnectError!Stream {
assert(ua.path.len <= max_len);
return .{ .socket = .{
.handle = try io.vtable.netConnectUnix(io.userdata, ua),
.address = .{ .ip4 = .loopback(0) },
} };
}
};
pub const ReceiveFlags = packed struct(u8) {
oob: bool = false,
peek: bool = false,
trunc: bool = false,
_: u5 = 0,
};
pub const IncomingMessage = struct {
from: IpAddress,
data: []u8,
control: []u8,
flags: Flags,
pub const init: IncomingMessage = .{
.from = undefined,
.data = undefined,
.control = &.{},
.flags = undefined,
};
pub const Flags = packed struct(u8) {
eor: bool,
trunc: bool,
ctrunc: bool,
oob: bool,
errqueue: bool,
_: u3 = 0,
};
};
pub const OutgoingMessage = struct {
address: *const IpAddress,
data_ptr: [*]const u8,
data_len: usize,
control: []const u8 = &.{},
};
pub const SendFlags = packed struct(u8) {
confirm: bool = false,
dont_route: bool = false,
eor: bool = false,
oob: bool = false,
fastopen: bool = false,
_: u3 = 0,
};
pub const ShutdownHow = enum { recv, send, both };
pub const ShutdownError = error{
ConnectionAborted,
ConnectionResetByPeer,
NetworkDown,
SocketUnconnected,
SystemResources,
} || Io.UnexpectedError || Io.Cancelable;
pub const Interface = struct {
index: u32,
pub const none: Interface = .{ .index = 0 };
pub const Name = struct {
bytes: [max_len:0]u8,
pub const max_len = if (@TypeOf(std.posix.IFNAMESIZE) == void) 0 else std.posix.IFNAMESIZE - 1;
pub fn toSlice(n: *const Name) []const u8 {
return std.mem.sliceTo(&n.bytes, 0);
}
pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name {
if (bytes.len > max_len) return error.NameTooLong;
return .fromSliceUnchecked(bytes);
}
pub fn fromSliceUnchecked(bytes: []const u8) Name {
assert(bytes.len <= max_len);
var result: Name = undefined;
@memcpy(result.bytes[0..bytes.len], bytes);
result.bytes[bytes.len] = 0;
return result;
}
pub const ResolveError = error{
InterfaceNotFound,
AccessDenied,
SystemResources,
} || Io.UnexpectedError || Io.Cancelable;
pub fn resolve(n: *const Name, io: Io) ResolveError!Interface {
return io.vtable.netInterfaceNameResolve(io.userdata, n);
}
};
pub const NameError = error{
InterfaceNotFound,
NameTooLong,
} || Io.UnexpectedError || Io.Cancelable;
pub fn name(i: Interface, io: Io) NameError!Name {
assert(i.index != 0);
return io.vtable.netInterfaceName(io.userdata, i);
}
pub fn isNone(i: Interface) bool {
return i.index == 0;
}
};
pub const Socket = struct {
handle: Handle,
address: IpAddress,
pub const Mode = enum {
stream,
dgram,
seqpacket,
raw,
rdm,
};
pub const Handle = std.posix.fd_t;
pub fn close(s: *const Socket, io: Io) void {
io.vtable.netClose(io.userdata, (&s.handle)[0..1]);
}
pub fn closeMany(io: Io, sockets: []const Socket) void {
io.vtable.netClose(io.userdata, sockets);
}
pub const SendError = error{
MessageOversize,
SystemResources,
NetworkUnreachable,
HostUnreachable,
NetworkDown,
ConnectionRefused,
AddressFamilyUnsupported,
FastOpenAlreadyInProgress,
ConnectionResetByPeer,
SocketUnconnected,
AccessDenied,
} || Io.UnexpectedError || Io.Cancelable;
pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void {
var message: OutgoingMessage = .{ .address = dest, .data_ptr = data.ptr, .data_len = data.len };
const err, const n = io.vtable.netSend(io.userdata, s.handle, (&message)[0..1], .{});
if (n != 1) return err.?;
if (message.data_len != data.len) return error.MessageOversize;
}
pub fn sendMany(s: *const Socket, io: Io, messages: []OutgoingMessage, flags: SendFlags) SendError!void {
const err, const n = io.vtable.netSend(io.userdata, s.handle, messages, flags);
if (n != messages.len) return err.?;
}
pub const ReceiveError = Io.Operation.NetReceive.Error || Io.Cancelable;
pub fn receive(s: *const Socket, io: Io, buffer: []u8) ReceiveError!IncomingMessage {
var message: IncomingMessage = .init;
const maybe_err, const count = (try io.operate(.{ .net_receive = .{
.socket_handle = s.handle,
.message_buffer = (&message)[0..1],
.data_buffer = buffer,
.flags = .{},
} })).net_receive;
if (maybe_err) |err| return err;
assert(1 == count);
return message;
}
pub const ReceiveTimeoutError = ReceiveError || Io.Timeout.Error || Io.ConcurrentError;
pub fn receiveTimeout(
s: *const Socket,
io: Io,
buffer: []u8,
timeout: Io.Timeout,
) ReceiveTimeoutError!IncomingMessage {
var message: IncomingMessage = .init;
const maybe_err, const count = (try io.operateTimeout(.{ .net_receive = .{
.socket_handle = s.handle,
.message_buffer = (&message)[0..1],
.data_buffer = buffer,
.flags = .{},
} }, timeout)).net_receive;
if (maybe_err) |err| return err;
assert(1 == count);
return message;
}
pub fn receiveManyTimeout(
s: *const Socket,
io: Io,
message_buffer: []IncomingMessage,
data_buffer: []u8,
flags: ReceiveFlags,
timeout: Io.Timeout,
) struct { ?ReceiveTimeoutError, usize } {
const result = io.operateTimeout(.{ .net_receive = .{
.socket_handle = s.handle,
.message_buffer = message_buffer,
.data_buffer = data_buffer,
.flags = flags,
} }, timeout) catch |err| return .{ err, 0 };
return result.net_receive;
}
pub const CreatePairError = error{
OperationUnsupported,
AccessDenied,
AddressFamilyUnsupported,
ProtocolUnsupportedBySystem,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
SystemResources,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
} || Io.UnexpectedError || Io.Cancelable;
pub const CreatePairOptions = struct {
family: IpAddress.Family = .ip4,
mode: Mode = .stream,
protocol: ?Protocol = null,
};
pub fn createPair(io: Io, options: CreatePairOptions) CreatePairError![2]Socket {
return io.vtable.netSocketCreatePair(io.userdata, options);
}
};
pub const Stream = struct {
socket: Socket,
const max_iovecs_len = 8;
pub fn read(s: *const Stream, io: Io, data: [][]u8) Reader.Error!usize {
return (try io.operate(.{ .net_read = .{
.socket_handle = s.socket.handle,
.data = data,
} })).net_read;
}
pub fn close(s: *const Stream, io: Io) void {
io.vtable.netClose(io.userdata, (&s.socket.handle)[0..1]);
}
pub fn shutdown(s: *const Stream, io: Io, how: ShutdownHow) ShutdownError!void {
return io.vtable.netShutdown(io.userdata, s.socket.handle, how);
}
pub const Reader = struct {
io: Io,
interface: Io.Reader,
stream: Stream,
err: ?Error,
pub const Error = Io.Operation.NetRead.Error || Io.Cancelable;
pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {
return .{
.io = io,
.interface = .{
.vtable = &.{
.stream = streamImpl,
.readVec = readVec,
},
.buffer = buffer,
.seek = 0,
.end = 0,
},
.stream = stream,
.err = null,
};
}
fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
const dest = limit.slice(try io_w.writableSliceGreedy(1));
var data: [1][]u8 = .{dest};
const n = try readVec(io_r, &data);
io_w.advance(n);
return n;
}
fn readVec(io_r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
const io = r.io;
var iovecs_buffer: [max_iovecs_len][]u8 = undefined;
const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data);
const dest = iovecs_buffer[0..dest_n];
assert(dest[0].len > 0);
const n = r.stream.read(io, dest) catch |err| {
r.err = err;
return error.ReadFailed;
};
if (n == 0) {
return error.EndOfStream;
}
if (n > data_size) {
r.interface.end += n - data_size;
return data_size;
}
return n;
}
};
pub const Writer = struct {
io: Io,
interface: Io.Writer,
stream: Stream,
err: ?Error = null,
write_file_err: ?WriteFileError = null,
pub const Error = error{
FastOpenAlreadyInProgress,
ConnectionResetByPeer,
SystemResources,
NetworkUnreachable,
HostUnreachable,
NetworkDown,
ConnectionRefused,
AddressFamilyUnsupported,
SocketUnconnected,
SocketNotBound,
} || Io.UnexpectedError || Io.Cancelable;
pub const WriteFileError = Error || error{
Unimplemented,
EndOfStream,
ReadFailed,
};
pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
return .{
.io = io,
.stream = stream,
.interface = .{
.vtable = &.{
.drain = drain,
.sendFile = sendFile,
},
.buffer = buffer,
},
};
}
fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
const io = w.io;
const buffered = io_w.buffered();
const handle = w.stream.socket.handle;
const n = io.vtable.netWrite(io.userdata, handle, buffered, data, splat) catch |err| {
w.err = err;
return error.WriteFailed;
};
return io_w.consume(n);
}
fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
const io = w.io;
const header = io_w.buffered();
const handle = w.stream.socket.handle;
const n = io.vtable.netWriteFile(io.userdata, handle, header, file_reader, limit) catch |err| switch (err) {
error.Canceled => {
w.err = error.Canceled;
return error.WriteFailed;
},
error.EndOfStream,
error.Unimplemented,
error.ReadFailed,
=> |e| return e,
else => |e| {
w.write_file_err = e;
return error.WriteFailed;
},
};
return io_w.consume(n);
}
};
pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {
return .init(stream, io, buffer);
}
pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer {
return .init(stream, io, buffer);
}
};
pub const Server = struct {
socket: Socket,
options: AcceptOptions,
pub fn deinit(s: *Server, io: Io) void {
s.socket.close(io);
s.* = undefined;
}
pub const AcceptError = error{
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
SystemResources,
SocketNotListening,
NetworkDown,
WouldBlock,
ConnectionAborted,
BlockedByFirewall,
ProtocolFailure,
} || Io.UnexpectedError || Io.Cancelable;
pub const AcceptOptions = switch (native_os) {
.windows => struct { mode: Socket.Mode, protocol: ?Protocol },
else => void,
};
pub fn accept(s: *Server, io: Io) AcceptError!Stream {
return .{ .socket = try io.vtable.netAccept(io.userdata, s.socket.handle, s.options) };
}
};
test "parsing IPv6 addresses" {
try testIp6Parse("fe80::e0e:76ff:fed4:cf22%eno1");
try testIp6Parse("2001:db8::1");
try testIp6ParseTransform("2001:db8::1", "2001:0db8:0000:0000:0000:0000:0000:0001");
try testIp6Parse("::1");
try testIp6Parse("::");
try testIp6Parse("fe80::1");
try testIp6Parse("fe80::abcd:ef12%3");
try testIp6Parse("ff02::");
try testIp6Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
}
test "IpAddress.setPort works" {
var addr: IpAddress = .{ .ip4 = undefined };
addr.setPort(0);
try std.testing.expectEqual(0, addr.getPort());
}
fn testIp6Parse(input: []const u8) !void {
return testIp6ParseTransform(input, input);
}
fn testIp6ParseTransform(expected: []const u8, input: []const u8) !void {
const ua = switch (Ip6Address.Unresolved.parse(input)) {
.success => |p| p,
else => |x| {
std.debug.print("failed to parse \"{s}\": {any}\n", .{ input, x });
return error.TestFailed;
},
};
var buffer: [100]u8 = undefined;
const result = try std.fmt.bufPrint(&buffer, "{f}", .{ua});
try std.testing.expectEqualStrings(expected, result);
}
test {
_ = HostName;
_ = @import("net/test.zig");
}