authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-05 20:27:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:48-07:00
logb428612a202a76f7a0aee18bde00c104753f3e60
tree93f059f67c5ddee228e5e2e18a81e6bde96b4b1b
parent774df26835069039ba739828a7619393de01a5f2

WIP: hack away at std.Io return flight


10 files changed, 339 insertions(+), 250 deletions(-)

lib/std/Io.zig+26-19
......@@ -719,31 +719,38 @@ pub const Timestamp = struct {
719719 ///
720720 /// The epoch is implementation-defined. For example NTFS/Windows uses
721721 /// 1601-01-01.
722 realtime,
722 real,
723723 /// A nonsettable system-wide clock that represents time since some
724724 /// unspecified point in the past.
725725 ///
726 /// On Linux, corresponds to how long the system has been running since
727 /// it booted.
726 /// Monotonic: Guarantees that the time returned by consecutive calls
727 /// will not go backwards, but successive calls may return identical
728 /// (not-increased) time values.
728729 ///
729730 /// Not affected by discontinuous jumps in the system time (e.g., if
730 /// the system administrator manually changes the clock), but is
731 /// affected by frequency adjustments. **This clock does not count time
732 /// that the system is suspended.**
731 /// the system administrator manually changes the clock), but may be
732 /// affected by frequency adjustments.
733733 ///
734 /// Guarantees that the time returned by consecutive calls will not go
735 /// backwards, but successive calls may return identical
736 /// (not-increased) time values.
734 /// This clock expresses intent to **exclude time that the system is
735 /// suspended**. However, implementations may be unable to satisify
736 /// this, and may include that time.
737 ///
738 /// * On Linux, corresponds `CLOCK_MONOTONIC`.
739 /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
740 awake,
741 /// Identical to `awake` except it expresses intent to include time
742 /// that the system is suspended, however, it may be implemented
743 /// identically to `awake`.
737744 ///
738 /// May or may not include time the system is suspended, but
739 /// implementations should exclude that time if possible.
740 monotonic,
741 /// Identical to `monotonic` except it also includes any time that the
742 /// system is suspended, if possible. However, it may be implemented
743 /// identically to `monotonic`.
744 boottime,
745 process_cputime_id,
746 thread_cputime_id,
745 /// * On Linux, corresponds `CLOCK_BOOTTIME`.
746 /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
747 boot,
748 /// Tracks the amount of CPU in user or kernel mode used by the calling
749 /// process.
750 cpu_process,
751 /// Tracks the amount of CPU in user or kernel mode used by the calling
752 /// thread.
753 cpu_thread,
747754 };
748755
749756 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
......@@ -825,7 +832,7 @@ pub const Duration = struct {
825832 }
826833
827834 pub fn sleep(duration: Duration, io: Io) SleepError!void {
828 return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .monotonic } });
835 return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } });
829836 }
830837};
831838
lib/std/Io/File.zig+5
......@@ -319,6 +319,11 @@ pub const Reader = struct {
319319 };
320320 }
321321
322 /// Takes a legacy `std.fs.File` to help with upgrading.
323 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
324 return .init(.{ .handle = file.handle }, io, buffer);
325 }
326
322327 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
323328 return .{
324329 .io = io,
lib/std/Io/Threaded.zig+23-12
......@@ -1032,7 +1032,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err
10321032 // and uses the NTFS/Windows epoch, which is 1601-01-01.
10331033 return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100;
10341034 },
1035 .monotonic, .boottime => {
1035 .monotonic, .uptime => {
10361036 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
10371037 return .{ .timestamp = windows.QueryPerformanceCounter() };
10381038 },
......@@ -1132,7 +1132,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
11321132 .sec = std.math.maxInt(sec_type),
11331133 .nsec = std.math.maxInt(nsec_type),
11341134 };
1135 if (d.clock != .monotonic) return error.UnsupportedClock;
1135 // TODO check which clock nanosleep uses on this host
1136 // and return error.UnsupportedClock if it does not match
11361137 const ns = d.duration.nanoseconds;
11371138 break :t .{
11381139 .sec = @intCast(@divFloor(ns, std.time.ns_per_s)),
......@@ -1331,11 +1332,15 @@ fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, optio
13311332fn ipConnectPosix(
13321333 userdata: ?*anyopaque,
13331334 address: *const Io.net.IpAddress,
1334 options: Io.net.IpAddress.BindOptions,
1335 options: Io.net.IpAddress.ConnectOptions,
13351336) Io.net.IpAddress.ConnectError!Io.net.Stream {
1337 if (options.timeout != .none) @panic("TODO");
13361338 const pool: *Pool = @ptrCast(@alignCast(userdata));
13371339 const family = posixAddressFamily(address);
1338 const socket_fd = try openSocketPosix(pool, family, options);
1340 const socket_fd = try openSocketPosix(pool, family, .{
1341 .mode = options.mode,
1342 .protocol = options.protocol,
1343 });
13391344 var storage: PosixAddress = undefined;
13401345 var addr_len = addressToPosix(address, &storage);
13411346 try posixConnect(pool, socket_fd, &storage.any, addr_len);
......@@ -1490,11 +1495,11 @@ fn netSend(
14901495 const pool: *Pool = @ptrCast(@alignCast(userdata));
14911496
14921497 const posix_flags: u32 =
1493 @as(u32, if (flags.confirm) posix.MSG.CONFIRM else 0) |
1498 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
14941499 @as(u32, if (flags.dont_route) posix.MSG.DONTROUTE else 0) |
14951500 @as(u32, if (flags.eor) posix.MSG.EOR else 0) |
14961501 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
1497 @as(u32, if (flags.fastopen) posix.MSG.FASTOPEN else 0) |
1502 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
14981503 posix.MSG.NOSIGNAL;
14991504
15001505 var i: usize = 0;
......@@ -2024,11 +2029,17 @@ fn recoverableOsBugDetected() void {
20242029
20252030fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {
20262031 return switch (clock) {
2027 .realtime => posix.CLOCK.REALTIME,
2028 .monotonic => posix.CLOCK.MONOTONIC,
2029 .boottime => posix.CLOCK.BOOTTIME,
2030 .process_cputime_id => posix.CLOCK.PROCESS_CPUTIME_ID,
2031 .thread_cputime_id => posix.CLOCK.THREAD_CPUTIME_ID,
2032 .real => posix.CLOCK.REALTIME,
2033 .awake => switch (builtin.os.tag) {
2034 .macos, .ios, .watchos, .tvos => posix.CLOCK.UPTIME_RAW,
2035 else => posix.CLOCK.MONOTONIC,
2036 },
2037 .boot => switch (builtin.os.tag) {
2038 .macos, .ios, .watchos, .tvos => posix.CLOCK.MONOTONIC_RAW,
2039 else => posix.CLOCK.BOOTTIME,
2040 },
2041 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID,
2042 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID,
20322043 };
20332044}
20342045
......@@ -2036,7 +2047,7 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t {
20362047 return switch (clock) {
20372048 .realtime => .REALTIME,
20382049 .monotonic => .MONOTONIC,
2039 .boottime => .MONOTONIC,
2050 .uptime => .MONOTONIC,
20402051 .process_cputime_id => .PROCESS_CPUTIME_ID,
20412052 .thread_cputime_id => .THREAD_CPUTIME_ID,
20422053 };
lib/std/Io/net.zig+28-9
......@@ -186,7 +186,7 @@ pub const IpAddress = union(enum) {
186186 /// Waits for a TCP connection. When using this API, `bind` does not need
187187 /// to be called. The returned `Server` has an open `stream`.
188188 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
189 return io.vtable.tcpListen(io.userdata, address, options);
189 return io.vtable.listen(io.userdata, address, options);
190190 }
191191
192192 pub const BindError = error{
......@@ -236,6 +236,8 @@ pub const IpAddress = union(enum) {
236236 AddressInUse,
237237 AddressUnavailable,
238238 AddressFamilyUnsupported,
239 /// Insufficient memory or other resource internal to the operating system.
240 SystemResources,
239241 ConnectionPending,
240242 ConnectionRefused,
241243 ConnectionResetByPeer,
......@@ -246,12 +248,23 @@ pub const IpAddress = union(enum) {
246248 /// One of the `ConnectOptions` is not supported by the Io
247249 /// implementation.
248250 OptionUnsupported,
249 } || Io.UnexpectedError || Io.Cancelable;
251 /// Per-process limit on the number of open file descriptors has been reached.
252 ProcessFdQuotaExceeded,
253 /// System-wide limit on the total number of open files has been reached.
254 SystemFdQuotaExceeded,
255 ProtocolUnsupportedBySystem,
256 ProtocolUnsupportedByAddressFamily,
257 SocketModeUnsupported,
258 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
250259
251 pub const ConnectOptions = BindOptions;
260 pub const ConnectOptions = struct {
261 mode: Socket.Mode,
262 protocol: ?Protocol = null,
263 timeout: Io.Timeout = .none,
264 };
252265
253266 /// Initiates a connection-oriented network stream.
254 pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
267 pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
255268 return io.vtable.ipConnect(io.userdata, address, options);
256269 }
257270};
......@@ -997,7 +1010,7 @@ pub const Stream = struct {
9971010 socket: Socket,
9981011
9991012 pub fn close(s: *Stream, io: Io) void {
1000 io.vtable.netClose(io.userdata, s.socket);
1013 io.vtable.netClose(io.userdata, s.socket.handle);
10011014 s.* = undefined;
10021015 }
10031016
......@@ -1040,10 +1053,13 @@ pub const Stream = struct {
10401053 return n;
10411054 }
10421055
1043 fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize {
1056 fn readVec(io_r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
10441057 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
10451058 const io = r.io;
1046 return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data);
1059 return io.vtable.netRead(io.userdata, r.stream, data) catch |err| {
1060 r.err = err;
1061 return error.ReadFailed;
1062 };
10471063 }
10481064 };
10491065
......@@ -1078,7 +1094,10 @@ pub const Stream = struct {
10781094 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
10791095 const io = w.io;
10801096 const buffered = io_w.buffered();
1081 const n = try io.vtable.netWrite(io.vtable.userdata, w.stream, buffered, data, splat);
1097 const n = io.vtable.netWrite(io.userdata, w.stream, buffered, data, splat) catch |err| {
1098 w.err = err;
1099 return error.WriteFailed;
1100 };
10821101 return io_w.consume(n);
10831102 }
10841103 };
......@@ -1104,7 +1123,7 @@ pub const Server = struct {
11041123
11051124 /// Blocks until a client connects to the server.
11061125 pub fn accept(s: *Server, io: Io) AcceptError!Stream {
1107 return io.vtable.accept(io, s);
1126 return io.vtable.accept(io.userdata, s);
11081127 }
11091128};
11101129
lib/std/Io/net/HostName.zig+51-13
......@@ -19,12 +19,12 @@ bytes: []const u8,
1919
2020pub const max_len = 255;
2121
22pub const InitError = error{
22pub const ValidateError = error{
2323 NameTooLong,
2424 InvalidHostName,
2525};
2626
27pub fn init(bytes: []const u8) InitError!HostName {
27pub fn validate(bytes: []const u8) ValidateError!void {
2828 if (bytes.len > max_len) return error.NameTooLong;
2929 if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;
3030 for (bytes) |byte| {
......@@ -33,10 +33,34 @@ pub fn init(bytes: []const u8) InitError!HostName {
3333 }
3434 return error.InvalidHostName;
3535 }
36}
37
38pub fn init(bytes: []const u8) ValidateError!HostName {
39 try validate(bytes);
3640 return .{ .bytes = bytes };
3741}
3842
39/// TODO add a retry field here
43pub fn sameParentDomain(parent_host: HostName, child_host: HostName) bool {
44 const parent_bytes = parent_host.bytes;
45 const child_bytes = child_host.bytes;
46 if (!std.ascii.endsWithIgnoreCase(child_bytes, parent_bytes)) return false;
47 if (child_bytes.len == parent_bytes.len) return true;
48 if (parent_bytes.len > child_bytes.len) return false;
49 return child_bytes[child_bytes.len - parent_bytes.len - 1] == '.';
50}
51
52test sameParentDomain {
53 try std.testing.expect(!sameParentDomain(try .init("foo.com"), try .init("bar.com")));
54 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("foo.com")));
55 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("bar.foo.com")));
56 try std.testing.expect(!sameParentDomain(try .init("bar.foo.com"), try .init("foo.com")));
57}
58
59/// Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
60pub fn eql(a: HostName, b: HostName) bool {
61 return std.ascii.eqlIgnoreCase(a.bytes, b.bytes);
62}
63
4064pub const LookupOptions = struct {
4165 port: u16,
4266 /// Must have at least length 2.
......@@ -266,15 +290,15 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
266290 var answers_remaining = answers.len;
267291 for (answers) |*answer| answer.len = 0;
268292
269 // boottime is chosen because time the computer is suspended should count
293 // boot clock is chosen because time the computer is suspended should count
270294 // against time spent waiting for external messages to arrive.
271 var now_ts = try Io.Timestamp.now(io, .boottime);
295 var now_ts = try Io.Timestamp.now(io, .boot);
272296 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
273297 const attempt_duration: Io.Duration = .{
274298 .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
275299 };
276300
277 send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boottime)) {
301 send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boot)) {
278302 const max_messages = queries_buffer.len * ResolvConf.max_nameservers;
279303 {
280304 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
......@@ -518,7 +542,7 @@ fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u
518542 return n;
519543}
520544
521pub const ExpandError = error{InvalidDnsPacket} || InitError;
545pub const ExpandError = error{InvalidDnsPacket} || ValidateError;
522546
523547/// Decompresses a DNS name.
524548///
......@@ -618,22 +642,36 @@ pub const DnsResponse = struct {
618642 }
619643};
620644
621pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
645pub const ConnectError = LookupError || IpAddress.ConnectError;
622646
623pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream {
647pub fn connect(
648 host_name: HostName,
649 io: Io,
650 port: u16,
651 options: IpAddress.ConnectOptions,
652) ConnectError!Stream {
624653 var addresses_buffer: [32]IpAddress = undefined;
654 var canonical_name_buffer: [HostName.max_len]u8 = undefined;
625655
626 const results = try lookup(host_name, .{
656 const results = try lookup(host_name, io, .{
627657 .port = port,
628658 .addresses_buffer = &addresses_buffer,
629 .canonical_name_buffer = &.{},
659 .canonical_name_buffer = &canonical_name_buffer,
630660 });
631661 const addresses = addresses_buffer[0..results.addresses_len];
632662
633663 if (addresses.len == 0) return error.UnknownHostName;
634664
635 for (addresses) |addr| {
636 return addr.connectTcp(io) catch |err| switch (err) {
665 // TODO instead of serially, use a Select API to send out
666 // the connections simultaneously and then keep the first
667 // successful one, canceling the rest.
668
669 // TODO On Linux this should additionally use an Io.Queue based
670 // DNS resolution API in order to send out a connection after
671 // each DNS response before waiting for the rest of them.
672
673 for (addresses) |*addr| {
674 return addr.connect(io, options) catch |err| switch (err) {
637675 error.ConnectionRefused => continue,
638676 else => |e| return e,
639677 };
lib/std/Io/net/test.zig+91-86
......@@ -7,32 +7,30 @@ const testing = std.testing;
77test "parse and render IP addresses at comptime" {
88 comptime {
99 const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable;
10 try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
10 try testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
1111
1212 const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable;
13 try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
13 try testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
1414
1515 try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0));
1616 try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0));
17 try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("::123.123.123.123", 0));
18 try testing.expectError(error.ParseFailed, net.IpAddress.resolveIp("127.01.0.1", 0));
1917 }
2018}
2119
2220test "format IPv6 address with no zero runs" {
23 const addr = try std.net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0);
24 try std.testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});
21 const addr = try net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0);
22 try testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});
2523}
2624
2725test "parse IPv6 addresses and check compressed form" {
28 try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
29 try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
26 try testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
27 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
3028 });
31 try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
32 try std.net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
29 try testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
30 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
3331 });
34 try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
35 try std.net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
32 try testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
33 try net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
3634 });
3735}
3836
......@@ -43,14 +41,14 @@ test "parse IPv6 address, check raw bytes" {
4341 0x00, 0x01, 0x00, 0x00, // :0001:0000
4442 0x00, 0x00, 0x00, 0x02, // :0000:0002
4543 };
46
47 const addr = try std.net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
48
49 const actual_raw = addr.in6.sa.addr[0..];
50 try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw);
44 const addr = try net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
45 try testing.expectEqualSlices(u8, &expected_raw, &addr.ip6.bytes);
5146}
5247
5348test "parse and render IPv6 addresses" {
49 // TODO make this test parsing and rendering only, then it doesn't need I/O
50 const io = testing.io;
51
5452 var buffer: [100]u8 = undefined;
5553 const ips = [_][]const u8{
5654 "FF01:0:0:0:0:0:0:FB",
......@@ -79,12 +77,12 @@ test "parse and render IPv6 addresses" {
7977 for (ips, 0..) |ip, i| {
8078 const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable;
8179 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
82 try std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
80 try testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
8381
8482 if (builtin.os.tag == .linux) {
85 const addr_via_resolve = net.IpAddress.resolveIp6(ip, 0) catch unreachable;
83 const addr_via_resolve = net.IpAddress.resolveIp6(io, ip, 0) catch unreachable;
8684 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable;
87 try std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
85 try testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
8886 }
8987 }
9088
......@@ -97,21 +95,23 @@ test "parse and render IPv6 addresses" {
9795 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0));
9896 // TODO Make this test pass on other operating systems.
9997 if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) {
100 try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6("ff01::fb%", 0));
98 try testing.expectError(error.Incomplete, net.IpAddress.resolveIp6(io, "ff01::fb%", 0));
10199 // Assumes IFNAMESIZE will always be a multiple of 2
102 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));
103 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6("ff01::fb%12345678901234", 0));
100 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));
101 try testing.expectError(error.Overflow, net.IpAddress.resolveIp6(io, "ff01::fb%12345678901234", 0));
104102 }
105103}
106104
107105test "invalid but parseable IPv6 scope ids" {
106 const io = testing.io;
107
108108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
109109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
110110 // TODO Make this test pass on other operating systems.
111111 return error.SkipZigTest;
112112 }
113113
114 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6("ff01::fb%123s45678901234", 0));
114 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0));
115115}
116116
117117test "parse and render IPv4 addresses" {
......@@ -125,7 +125,7 @@ test "parse and render IPv4 addresses" {
125125 }) |ip| {
126126 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;
127127 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
128 try std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
128 try testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
129129 }
130130
131131 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
......@@ -136,50 +136,43 @@ test "parse and render IPv4 addresses" {
136136 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));
137137}
138138
139test "parse and render UNIX addresses" {
140 if (builtin.os.tag == .wasi) return error.SkipZigTest;
141 if (!net.has_unix_sockets) return error.SkipZigTest;
142
143 const addr = net.Address.initUnix("/tmp/testpath") catch unreachable;
144 try std.testing.expectFmt("/tmp/testpath", "{f}", .{addr});
145
146 const too_long = [_]u8{'a'} ** 200;
147 try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..]));
148}
149
150139test "resolve DNS" {
151140 if (builtin.os.tag == .wasi) return error.SkipZigTest;
152141
153 if (builtin.os.tag == .windows) {
154 _ = try std.os.windows.WSAStartup(2, 2);
155 }
156 defer {
157 if (builtin.os.tag == .windows) {
158 std.os.windows.WSACleanup() catch unreachable;
159 }
160 }
142 const io = testing.io;
161143
162144 // Resolve localhost, this should not fail.
163145 {
164146 const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80);
165147 const localhost_v6 = try net.IpAddress.parse("::2", 80);
166148
167 const result = try net.getAddressList(testing.allocator, "localhost", 80);
168 defer result.deinit();
169 for (result.addrs) |addr| {
170 if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break;
149 var addresses_buffer: [8]net.IpAddress = undefined;
150 var canon_name_buffer: [net.HostName.max_len]u8 = undefined;
151 const result = try net.HostName.lookup(try .init("localhost"), io, .{
152 .port = 80,
153 .addresses_buffer = &addresses_buffer,
154 .canonical_name_buffer = &canon_name_buffer,
155 });
156 for (addresses_buffer[0..result.addresses_len]) |addr| {
157 if (addr.eql(&localhost_v4) or addr.eql(&localhost_v6)) break;
171158 } else @panic("unexpected address for localhost");
172159 }
173160
174161 {
175162 // The tests are required to work even when there is no Internet connection,
176163 // so some of these errors we must accept and skip the test.
177 const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {
164 var addresses_buffer: [8]net.IpAddress = undefined;
165 var canon_name_buffer: [net.HostName.max_len]u8 = undefined;
166 const result = net.HostName.lookup(try .init("example.com"), io, .{
167 .port = 80,
168 .addresses_buffer = &addresses_buffer,
169 .canonical_name_buffer = &canon_name_buffer,
170 }) catch |err| switch (err) {
178171 error.UnknownHostName => return error.SkipZigTest,
179 error.TemporaryNameServerFailure => return error.SkipZigTest,
172 error.NameServerFailure => return error.SkipZigTest,
180173 else => return err,
181174 };
182 result.deinit();
175 _ = result;
183176 }
184177}
185178
......@@ -187,6 +180,8 @@ test "listen on a port, send bytes, receive bytes" {
187180 if (builtin.single_threaded) return error.SkipZigTest;
188181 if (builtin.os.tag == .wasi) return error.SkipZigTest;
189182
183 const io = testing.io;
184
190185 if (builtin.os.tag == .windows) {
191186 _ = try std.os.windows.WSAStartup(2, 2);
192187 }
......@@ -198,28 +193,28 @@ test "listen on a port, send bytes, receive bytes" {
198193
199194 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
200195 // configured.
201 const localhost = try net.IpAddress.parse("127.0.0.1", 0);
196 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
202197
203 var server = try localhost.listen(.{});
204 defer server.deinit();
198 var server = try localhost.listen(io, .{});
199 defer server.deinit(io);
205200
206201 const S = struct {
207202 fn clientFn(server_address: net.IpAddress) !void {
208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();
203 var stream = try server_address.connect(io, .{ .mode = .stream });
204 defer stream.close(io);
210205
211 var stream_writer = socket.writer(&.{});
206 var stream_writer = stream.writer(io, &.{});
212207 try stream_writer.interface.writeAll("Hello world!");
213208 }
214209 };
215210
216 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
211 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address});
217212 defer t.join();
218213
219 var client = try server.accept();
220 defer client.stream.close();
214 var client = try server.accept(io);
215 defer client.stream.close(io);
221216 var buf: [16]u8 = undefined;
222 var stream_reader = client.stream.reader(&.{});
217 var stream_reader = client.stream.reader(io, &.{});
223218 const n = try stream_reader.interface().readSliceShort(&buf);
224219
225220 try testing.expectEqual(@as(usize, 12), n);
......@@ -232,13 +227,15 @@ test "listen on an in use port" {
232227 return error.SkipZigTest;
233228 }
234229
235 const localhost = try net.IpAddress.parse("127.0.0.1", 0);
230 const io = testing.io;
231
232 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
236233
237 var server1 = try localhost.listen(.{ .reuse_address = true });
238 defer server1.deinit();
234 var server1 = try localhost.listen(io, .{ .reuse_address = true });
235 defer server1.deinit(io);
239236
240 var server2 = try server1.listen_address.listen(.{ .reuse_address = true });
241 defer server2.deinit();
237 var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true });
238 defer server2.deinit(io);
242239}
243240
244241fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
......@@ -268,9 +265,11 @@ fn testClient(addr: net.IpAddress) anyerror!void {
268265fn testServer(server: *net.Server) anyerror!void {
269266 if (builtin.os.tag == .wasi) return error.SkipZigTest;
270267
271 var client = try server.accept();
268 const io = testing.io;
269
270 var client = try server.accept(io);
272271
273 const stream = client.stream.writer();
272 const stream = client.stream.writer(io);
274273 try stream.print("hello from server\n", .{});
275274}
276275
......@@ -278,6 +277,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
278277 if (builtin.single_threaded) return error.SkipZigTest;
279278 if (!net.has_unix_sockets) return error.SkipZigTest;
280279
280 const io = testing.io;
281
281282 if (builtin.os.tag == .windows) {
282283 _ = try std.os.windows.WSAStartup(2, 2);
283284 }
......@@ -293,15 +294,15 @@ test "listen on a unix socket, send bytes, receive bytes" {
293294 const socket_addr = try net.IpAddress.initUnix(socket_path);
294295 defer std.fs.cwd().deleteFile(socket_path) catch {};
295296
296 var server = try socket_addr.listen(.{});
297 defer server.deinit();
297 var server = try socket_addr.listen(io, .{});
298 defer server.deinit(io);
298299
299300 const S = struct {
300301 fn clientFn(path: []const u8) !void {
301 const socket = try net.connectUnixSocket(path);
302 defer socket.close();
302 var stream = try net.connectUnixSocket(path);
303 defer stream.close(io);
303304
304 var stream_writer = socket.writer(&.{});
305 var stream_writer = stream.writer(io, &.{});
305306 try stream_writer.interface.writeAll("Hello world!");
306307 }
307308 };
......@@ -309,10 +310,10 @@ test "listen on a unix socket, send bytes, receive bytes" {
309310 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
310311 defer t.join();
311312
312 var client = try server.accept();
313 defer client.stream.close();
313 var client = try server.accept(io);
314 defer client.stream.close(io);
314315 var buf: [16]u8 = undefined;
315 var stream_reader = client.stream.reader(&.{});
316 var stream_reader = client.stream.reader(io, &.{});
316317 const n = try stream_reader.interface().readSliceShort(&buf);
317318
318319 try testing.expectEqual(@as(usize, 12), n);
......@@ -324,14 +325,16 @@ test "listen on a unix socket with reuse_address option" {
324325 // Windows doesn't implement reuse port option.
325326 if (builtin.os.tag == .windows) return error.SkipZigTest;
326327
328 const io = testing.io;
329
327330 const socket_path = try generateFileName("socket.unix");
328331 defer testing.allocator.free(socket_path);
329332
330333 const socket_addr = try net.Address.initUnix(socket_path);
331334 defer std.fs.cwd().deleteFile(socket_path) catch {};
332335
333 var server = try socket_addr.listen(.{ .reuse_address = true });
334 server.deinit();
336 var server = try socket_addr.listen(io, .{ .reuse_address = true });
337 server.deinit(io);
335338}
336339
337340fn generateFileName(base_name: []const u8) ![]const u8 {
......@@ -351,19 +354,21 @@ test "non-blocking tcp server" {
351354 return error.SkipZigTest;
352355 }
353356
354 const localhost = try net.IpAddress.parse("127.0.0.1", 0);
355 var server = localhost.listen(.{ .force_nonblocking = true });
356 defer server.deinit();
357 const io = testing.io;
358
359 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
360 var server = localhost.listen(io, .{ .force_nonblocking = true });
361 defer server.deinit(io);
357362
358 const accept_err = server.accept();
363 const accept_err = server.accept(io);
359364 try testing.expectError(error.WouldBlock, accept_err);
360365
361 const socket_file = try net.tcpConnectToAddress(server.listen_address);
366 const socket_file = try net.tcpConnectToAddress(server.socket.address);
362367 defer socket_file.close();
363368
364 var client = try server.accept();
365 defer client.stream.close();
366 const stream = client.stream.writer();
369 var client = try server.accept(io);
370 defer client.stream.close(io);
371 const stream = client.stream.writer(io);
367372 try stream.print("hello from server\n", .{});
368373
369374 var buf: [100]u8 = undefined;
lib/std/Uri.zig+23-15
......@@ -1,45 +1,48 @@
1//! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>.
2//! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild.
1//! Uniform Resource Identifier (URI) parsing roughly adhering to
2//! <https://tools.ietf.org/html/rfc3986>. Does not do perfect grammar and
3//! character class checking, but should be robust against URIs in the wild.
34
45const std = @import("std.zig");
56const testing = std.testing;
67const Uri = @This();
78const Allocator = std.mem.Allocator;
89const Writer = std.Io.Writer;
10const HostName = std.Io.net.HostName;
911
1012scheme: []const u8,
1113user: ?Component = null,
1214password: ?Component = null,
15/// If non-null, already validated.
1316host: ?Component = null,
1417port: ?u16 = null,
1518path: Component = Component.empty,
1619query: ?Component = null,
1720fragment: ?Component = null,
1821
19pub const host_name_max = 255;
22pub const GetHostError = error{UriMissingHost};
2023
2124/// Returned value may point into `buffer` or be the original string.
2225///
23/// Suggested buffer length: `host_name_max`.
24///
2526/// See also:
2627/// * `getHostAlloc`
27pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 {
28pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName {
2829 const component = uri.host orelse return error.UriMissingHost;
29 return component.toRaw(buffer) catch |err| switch (err) {
30 error.NoSpaceLeft => return error.UriHostTooLong,
30 const bytes = component.toRaw(buffer) catch |err| switch (err) {
31 error.NoSpaceLeft => unreachable, // `host` already validated.
3132 };
33 return .{ .bytes = bytes };
3234}
3335
36pub const GetHostAllocError = GetHostError || error{OutOfMemory};
37
3438/// Returned value may point into `buffer` or be the original string.
3539///
3640/// See also:
3741/// * `getHost`
38pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 {
42pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError![]const u8 {
3943 const component = uri.host orelse return error.UriMissingHost;
40 const result = try component.toRawMaybeAlloc(arena);
41 if (result.len > host_name_max) return error.UriHostTooLong;
42 return result;
44 const bytes = try component.toRawMaybeAlloc(arena);
45 return .{ .bytes = bytes };
4346}
4447
4548pub const Component = union(enum) {
......@@ -397,7 +400,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
397400 .scheme = new_parsed.scheme,
398401 .user = new_parsed.user,
399402 .password = new_parsed.password,
400 .host = new_parsed.host,
403 .host = try validateHost(new_parsed.host),
401404 .port = new_parsed.port,
402405 .path = remove_dot_segments(new_path),
403406 .query = new_parsed.query,
......@@ -408,7 +411,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
408411 .scheme = base.scheme,
409412 .user = new_parsed.user,
410413 .password = new_parsed.password,
411 .host = host,
414 .host = try validateHost(host),
412415 .port = new_parsed.port,
413416 .path = remove_dot_segments(new_path),
414417 .query = new_parsed.query,
......@@ -430,7 +433,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
430433 .scheme = base.scheme,
431434 .user = base.user,
432435 .password = base.password,
433 .host = base.host,
436 .host = try validateHost(base.host),
434437 .port = base.port,
435438 .path = path,
436439 .query = query,
......@@ -438,6 +441,11 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
438441 };
439442}
440443
444fn validateHost(bytes: []const u8) []const u8 {
445 try HostName.validate(bytes);
446 return bytes;
447}
448
441449/// In-place implementation of RFC 3986, Section 5.2.4.
442450fn remove_dot_segments(path: []u8) Component {
443451 var in_i: usize = 0;
lib/std/fs/test.zig+1-1
......@@ -2281,7 +2281,7 @@ test "seekTo flushes buffered data" {
22812281 }
22822282
22832283 var read_buffer: [16]u8 = undefined;
2284 var file_reader: std.Io.File.Reader = .init(file, io, &read_buffer);
2284 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer);
22852285
22862286 var buf: [4]u8 = undefined;
22872287 try file_reader.interface.readSliceAll(&buf);
lib/std/http/Client.zig+55-69
......@@ -15,6 +15,7 @@ const assert = std.debug.assert;
1515const Io = std.Io;
1616const Writer = std.Io.Writer;
1717const Reader = std.Io.Reader;
18const HostName = std.Io.net.HostName;
1819
1920const Client = @This();
2021
......@@ -69,7 +70,7 @@ pub const ConnectionPool = struct {
6970
7071 /// The criteria for a connection to be considered a match.
7172 pub const Criteria = struct {
72 host: []const u8,
73 host: HostName,
7374 port: u16,
7475 protocol: Protocol,
7576 };
......@@ -89,7 +90,7 @@ pub const ConnectionPool = struct {
8990 if (connection.port != criteria.port) continue;
9091
9192 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
92 if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue;
93 if (!connection.host().eql(criteria.host)) continue;
9394
9495 pool.acquireUnsafe(connection);
9596 return connection;
......@@ -118,19 +119,19 @@ pub const ConnectionPool = struct {
118119 /// If the connection is marked as closing, it will be closed instead.
119120 ///
120121 /// Threadsafe.
121 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
122 pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void {
122123 pool.mutex.lock();
123124 defer pool.mutex.unlock();
124125
125126 pool.used.remove(&connection.pool_node);
126127
127 if (connection.closing or pool.free_size == 0) return connection.destroy();
128 if (connection.closing or pool.free_size == 0) return connection.destroy(io);
128129
129130 if (pool.free_len >= pool.free_size) {
130131 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));
131132 pool.free_len -= 1;
132133
133 popped.destroy();
134 popped.destroy(io);
134135 }
135136
136137 if (connection.proxied) {
......@@ -178,21 +179,21 @@ pub const ConnectionPool = struct {
178179 /// All future operations on the connection pool will deadlock.
179180 ///
180181 /// Threadsafe.
181 pub fn deinit(pool: *ConnectionPool) void {
182 pub fn deinit(pool: *ConnectionPool, io: Io) void {
182183 pool.mutex.lock();
183184
184185 var next = pool.free.first;
185186 while (next) |node| {
186187 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
187188 next = node.next;
188 connection.destroy();
189 connection.destroy(io);
189190 }
190191
191192 next = pool.used.first;
192193 while (next) |node| {
193194 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
194195 next = node.next;
195 connection.destroy();
196 connection.destroy(io);
196197 }
197198
198199 pool.* = undefined;
......@@ -242,19 +243,19 @@ pub const Connection = struct {
242243
243244 fn create(
244245 client: *Client,
245 remote_host: []const u8,
246 remote_host: HostName,
246247 port: u16,
247248 stream: Io.net.Stream,
248249 ) error{OutOfMemory}!*Plain {
249250 const gpa = client.allocator;
250 const alloc_len = allocLen(client, remote_host.len);
251 const alloc_len = allocLen(client, remote_host.bytes.len);
251252 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
252253 errdefer gpa.free(base);
253 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len];
254 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.bytes.len];
254255 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
255256 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
256257 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
257 @memcpy(host_buffer, remote_host);
258 @memcpy(host_buffer, remote_host.bytes);
258259 const plain: *Plain = @ptrCast(base);
259260 plain.* = .{
260261 .connection = .{
......@@ -263,7 +264,7 @@ pub const Connection = struct {
263264 .stream_reader = stream.reader(socket_read_buffer),
264265 .pool_node = .{},
265266 .port = port,
266 .host_len = @intCast(remote_host.len),
267 .host_len = @intCast(remote_host.bytes.len),
267268 .proxied = false,
268269 .closing = false,
269270 .protocol = .plain,
......@@ -283,9 +284,9 @@ pub const Connection = struct {
283284 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
284285 }
285286
286 fn host(plain: *Plain) []u8 {
287 fn host(plain: *Plain) HostName {
287288 const base: [*]u8 = @ptrCast(plain);
288 return base[@sizeOf(Plain)..][0..plain.connection.host_len];
289 return .{ .bytes = base[@sizeOf(Plain)..][0..plain.connection.host_len] };
289290 }
290291 };
291292
......@@ -295,15 +296,15 @@ pub const Connection = struct {
295296
296297 fn create(
297298 client: *Client,
298 remote_host: []const u8,
299 remote_host: HostName,
299300 port: u16,
300301 stream: Io.net.Stream,
301302 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
302303 const gpa = client.allocator;
303 const alloc_len = allocLen(client, remote_host.len);
304 const alloc_len = allocLen(client, remote_host.bytes.len);
304305 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
305306 errdefer gpa.free(base);
306 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];
307 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.bytes.len];
307308 // The TLS client wants enough buffer for the max encrypted frame
308309 // size, and the HTTP body reader wants enough buffer for the
309310 // entire HTTP header. This means we need a combined upper bound.
......@@ -313,7 +314,7 @@ pub const Connection = struct {
313314 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
314315 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];
315316 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
316 @memcpy(host_buffer, remote_host);
317 @memcpy(host_buffer, remote_host.bytes);
317318 const tls: *Tls = @ptrCast(base);
318319 tls.* = .{
319320 .connection = .{
......@@ -322,17 +323,17 @@ pub const Connection = struct {
322323 .stream_reader = stream.reader(socket_read_buffer),
323324 .pool_node = .{},
324325 .port = port,
325 .host_len = @intCast(remote_host.len),
326 .host_len = @intCast(remote_host.bytes.len),
326327 .proxied = false,
327328 .closing = false,
328329 .protocol = .tls,
329330 },
330331 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
331332 .client = std.crypto.tls.Client.init(
332 tls.connection.stream_reader.interface(),
333 &tls.connection.stream_reader.interface,
333334 &tls.connection.stream_writer.interface,
334335 .{
335 .host = .{ .explicit = remote_host },
336 .host = .{ .explicit = remote_host.bytes },
336337 .ca = .{ .bundle = client.ca_bundle },
337338 .ssl_key_log = client.ssl_key_log,
338339 .read_buffer = tls_read_buffer,
......@@ -359,9 +360,9 @@ pub const Connection = struct {
359360 client.write_buffer_size + client.tls_buffer_size;
360361 }
361362
362 fn host(tls: *Tls) []u8 {
363 fn host(tls: *Tls) HostName {
363364 const base: [*]u8 = @ptrCast(tls);
364 return base[@sizeOf(Tls)..][0..tls.connection.host_len];
365 return .{ .bytes = base[@sizeOf(Tls)..][0..tls.connection.host_len] };
365366 }
366367 };
367368
......@@ -384,7 +385,7 @@ pub const Connection = struct {
384385 return c.stream_reader.stream;
385386 }
386387
387 pub fn host(c: *Connection) []u8 {
388 pub fn host(c: *Connection) HostName {
388389 return switch (c.protocol) {
389390 .tls => {
390391 if (disable_tls) unreachable;
......@@ -400,8 +401,8 @@ pub const Connection = struct {
400401
401402 /// If this is called without calling `flush` or `end`, data will be
402403 /// dropped unsent.
403 pub fn destroy(c: *Connection) void {
404 c.getStream().close();
404 pub fn destroy(c: *Connection, io: Io) void {
405 c.stream_reader.stream.close(io);
405406 switch (c.protocol) {
406407 .tls => {
407408 if (disable_tls) unreachable;
......@@ -437,7 +438,7 @@ pub const Connection = struct {
437438 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
438439 return &tls.client.reader;
439440 },
440 .plain => c.stream_reader.interface(),
441 .plain => &c.stream_reader.interface,
441442 };
442443 }
443444
......@@ -866,6 +867,7 @@ pub const Request = struct {
866867
867868 /// Returns the request's `Connection` back to the pool of the `Client`.
868869 pub fn deinit(r: *Request) void {
870 const io = r.client.io;
869871 if (r.connection) |connection| {
870872 connection.closing = connection.closing or switch (r.reader.state) {
871873 .ready => false,
......@@ -880,7 +882,7 @@ pub const Request = struct {
880882 },
881883 else => true,
882884 };
883 r.client.connection_pool.release(connection);
885 r.client.connection_pool.release(connection, io);
884886 }
885887 r.* = undefined;
886888 }
......@@ -1182,6 +1184,7 @@ pub const Request = struct {
11821184 ///
11831185 /// `aux_buf` must outlive accesses to `Request.uri`.
11841186 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {
1187 const io = r.client.io;
11851188 const new_location = head.location orelse return error.HttpRedirectLocationMissing;
11861189 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
11871190 const location = aux_buf.*[0..new_location.len];
......@@ -1204,13 +1207,13 @@ pub const Request = struct {
12041207 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
12051208 const old_connection = r.connection.?;
12061209 const old_host = old_connection.host();
1207 var new_host_name_buffer: [Uri.host_name_max]u8 = undefined;
1210 var new_host_name_buffer: [HostName.max_len]u8 = undefined;
12081211 const new_host = try new_uri.getHost(&new_host_name_buffer);
12091212 const keep_privileged_headers =
12101213 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and
1211 sameParentDomain(old_host, new_host);
1214 old_host.sameParentDomain(new_host);
12121215
1213 r.client.connection_pool.release(old_connection);
1216 r.client.connection_pool.release(old_connection, io);
12141217 r.connection = null;
12151218
12161219 if (!keep_privileged_headers) {
......@@ -1266,7 +1269,7 @@ pub const Request = struct {
12661269
12671270pub const Proxy = struct {
12681271 protocol: Protocol,
1269 host: []const u8,
1272 host: HostName,
12701273 authorization: ?[]const u8,
12711274 port: u16,
12721275 supports_connect: bool,
......@@ -1277,9 +1280,10 @@ pub const Proxy = struct {
12771280/// All pending requests must be de-initialized and all active connections released
12781281/// before calling this function.
12791282pub fn deinit(client: *Client) void {
1283 const io = client.io;
12801284 assert(client.connection_pool.used.first == null); // There are still active requests.
12811285
1282 client.connection_pool.deinit();
1286 client.connection_pool.deinit(io);
12831287 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
12841288
12851289 client.* = undefined;
......@@ -1385,7 +1389,7 @@ pub const basic_authorization = struct {
13851389 }
13861390};
13871391
1388pub const ConnectTcpError = Allocator.Error || error{
1392pub const ConnectTcpError = error{
13891393 ConnectionRefused,
13901394 NetworkUnreachable,
13911395 ConnectionTimedOut,
......@@ -1393,17 +1397,16 @@ pub const ConnectTcpError = Allocator.Error || error{
13931397 TemporaryNameServerFailure,
13941398 NameServerFailure,
13951399 UnknownHostName,
1396 HostLacksNetworkAddresses,
13971400 UnexpectedConnectFailure,
13981401 TlsInitializationFailed,
1399};
1402} || Allocator.Error || Io.Cancelable;
14001403
14011404/// Reuses a `Connection` if one matching `host` and `port` is already open.
14021405///
14031406/// Threadsafe.
14041407pub fn connectTcp(
14051408 client: *Client,
1406 host: []const u8,
1409 host: HostName,
14071410 port: u16,
14081411 protocol: Protocol,
14091412) ConnectTcpError!*Connection {
......@@ -1411,16 +1414,17 @@ pub fn connectTcp(
14111414}
14121415
14131416pub const ConnectTcpOptions = struct {
1414 host: Io.net.HostName,
1417 host: HostName,
14151418 port: u16,
14161419 protocol: Protocol,
14171420
1418 proxied_host: ?[]const u8 = null,
1421 proxied_host: ?HostName = null,
14191422 proxied_port: ?u16 = null,
1423 timeout: Io.Timeout = .none,
14201424};
14211425
14221426pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1423 const host = options.host_name;
1427 const host = options.host;
14241428 const port = options.port;
14251429 const protocol = options.protocol;
14261430
......@@ -1433,17 +1437,15 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
14331437 .protocol = protocol,
14341438 })) |conn| return conn;
14351439
1436 const stream = host.connectTcp(client.io, port) catch |err| switch (err) {
1440 const stream = host.connect(client.io, port, .{ .mode = .stream }) catch |err| switch (err) {
14371441 error.ConnectionRefused => return error.ConnectionRefused,
14381442 error.NetworkUnreachable => return error.NetworkUnreachable,
14391443 error.ConnectionTimedOut => return error.ConnectionTimedOut,
14401444 error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
1441 error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure,
14421445 error.NameServerFailure => return error.NameServerFailure,
14431446 error.UnknownHostName => return error.UnknownHostName,
1444 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
14451447 error.Canceled => return error.Canceled,
1446 else => return error.UnexpectedConnectFailure,
1448 //else => return error.UnexpectedConnectFailure,
14471449 };
14481450 errdefer stream.close();
14491451
......@@ -1479,7 +1481,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
14791481 errdefer client.allocator.destroy(conn);
14801482 conn.* = .{ .data = undefined };
14811483
1482 const stream = try std.net.connectUnixSocket(path);
1484 const stream = try Io.net.connectUnixSocket(path);
14831485 errdefer stream.close();
14841486
14851487 conn.data = .{
......@@ -1504,9 +1506,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
15041506pub fn connectProxied(
15051507 client: *Client,
15061508 proxy: *Proxy,
1507 proxied_host: []const u8,
1509 proxied_host: HostName,
15081510 proxied_port: u16,
15091511) !*Connection {
1512 const io = client.io;
15101513 if (!proxy.supports_connect) return error.TunnelNotSupported;
15111514
15121515 if (client.connection_pool.findConnection(.{
......@@ -1526,12 +1529,12 @@ pub fn connectProxied(
15261529 });
15271530 errdefer {
15281531 connection.closing = true;
1529 client.connection_pool.release(connection);
1532 client.connection_pool.release(connection, io);
15301533 }
15311534
15321535 var req = client.request(.CONNECT, .{
15331536 .scheme = "http",
1534 .host = .{ .raw = proxied_host },
1537 .host = .{ .raw = proxied_host.bytes },
15351538 .port = proxied_port,
15361539 }, .{
15371540 .redirect_behavior = .unhandled,
......@@ -1576,7 +1579,7 @@ pub const ConnectError = ConnectTcpError || RequestError;
15761579/// This function is threadsafe.
15771580pub fn connect(
15781581 client: *Client,
1579 host: []const u8,
1582 host: HostName,
15801583 port: u16,
15811584 protocol: Protocol,
15821585) ConnectError!*Connection {
......@@ -1586,9 +1589,7 @@ pub fn connect(
15861589 } orelse return client.connectTcp(host, port, protocol);
15871590
15881591 // Prevent proxying through itself.
1589 if (std.ascii.eqlIgnoreCase(proxy.host, host) and
1590 proxy.port == port and proxy.protocol == protocol)
1591 {
1592 if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) {
15921593 return client.connectTcp(host, port, protocol);
15931594 }
15941595
......@@ -1608,7 +1609,6 @@ pub fn connect(
16081609pub const RequestError = ConnectTcpError || error{
16091610 UnsupportedUriScheme,
16101611 UriMissingHost,
1611 UriHostTooLong,
16121612 CertificateBundleLoadFailure,
16131613};
16141614
......@@ -1697,7 +1697,7 @@ pub fn request(
16971697 }
16981698
16991699 const connection = options.connection orelse c: {
1700 var host_name_buffer: [Uri.host_name_max]u8 = undefined;
1700 var host_name_buffer: [HostName.max_len]u8 = undefined;
17011701 const host_name = try uri.getHost(&host_name_buffer);
17021702 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);
17031703 };
......@@ -1835,20 +1835,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
18351835 return .{ .status = response.head.status };
18361836}
18371837
1838pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool {
1839 if (!std.ascii.endsWithIgnoreCase(child_host, parent_host)) return false;
1840 if (child_host.len == parent_host.len) return true;
1841 if (parent_host.len > child_host.len) return false;
1842 return child_host[child_host.len - parent_host.len - 1] == '.';
1843}
1844
1845test sameParentDomain {
1846 try testing.expect(!sameParentDomain("foo.com", "bar.com"));
1847 try testing.expect(sameParentDomain("foo.com", "foo.com"));
1848 try testing.expect(sameParentDomain("foo.com", "bar.foo.com"));
1849 try testing.expect(!sameParentDomain("bar.foo.com", "foo.com"));
1850}
1851
18521838test {
18531839 _ = Response;
18541840}
lib/std/http/test.zig+36-26
......@@ -53,7 +53,7 @@ test "trailers" {
5353
5454 const gpa = std.testing.allocator;
5555
56 var client: http.Client = .{ .allocator = gpa };
56 var client: http.Client = .{ .allocator = gpa, .io = io };
5757 defer client.deinit();
5858
5959 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
......@@ -141,12 +141,13 @@ test "HTTP server handles a chunked transfer coding request" {
141141 "0\r\n" ++
142142 "\r\n";
143143
144 const gpa = std.testing.allocator;
145 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
144 const host_name: net.HostName = try .init("127.0.0.1");
145 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
146146 defer stream.close(io);
147 var stream_writer = stream.writer(&.{});
147 var stream_writer = stream.writer(io, &.{});
148148 try stream_writer.interface.writeAll(request_bytes);
149149
150 const gpa = std.testing.allocator;
150151 const expected_response =
151152 "HTTP/1.1 200 OK\r\n" ++
152153 "connection: close\r\n" ++
......@@ -154,8 +155,8 @@ test "HTTP server handles a chunked transfer coding request" {
154155 "content-type: text/plain\r\n" ++
155156 "\r\n" ++
156157 "message from server!\n";
157 var stream_reader = stream.reader(&.{});
158 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1));
158 var stream_reader = stream.reader(io, &.{});
159 const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1));
159160 defer gpa.free(response);
160161 try expectEqualStrings(expected_response, response);
161162}
......@@ -241,7 +242,7 @@ test "echo content server" {
241242 defer test_server.destroy();
242243
243244 {
244 var client: http.Client = .{ .allocator = std.testing.allocator };
245 var client: http.Client = .{ .allocator = std.testing.allocator, .io = io };
245246 defer client.deinit();
246247
247248 try echoTests(&client, test_server.port());
......@@ -294,14 +295,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
294295 defer test_server.destroy();
295296
296297 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";
297 const gpa = std.testing.allocator;
298 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
298 const host_name: net.HostName = try .init("127.0.0.1");
299 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
299300 defer stream.close(io);
300 var stream_writer = stream.writer(&.{});
301 var stream_writer = stream.writer(io, &.{});
301302 try stream_writer.interface.writeAll(request_bytes);
302303
303 var stream_reader = stream.reader(&.{});
304 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
304 var stream_reader = stream.reader(io, &.{});
305 const gpa = std.testing.allocator;
306 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
305307 defer gpa.free(response);
306308
307309 var expected_response = std.array_list.Managed(u8).init(gpa);
......@@ -366,14 +368,15 @@ test "receiving arbitrary http headers from the client" {
366368 "CoNneCtIoN:close\r\n" ++
367369 "aoeu: asdf \r\n" ++
368370 "\r\n";
369 const gpa = std.testing.allocator;
370 var stream = try net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
371 const host_name: net.HostName = try .init("127.0.0.1");
372 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
371373 defer stream.close(io);
372 var stream_writer = stream.writer(&.{});
374 var stream_writer = stream.writer(io, &.{});
373375 try stream_writer.interface.writeAll(request_bytes);
374376
375 var stream_reader = stream.reader(&.{});
376 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
377 var stream_reader = stream.reader(io, &.{});
378 const gpa = std.testing.allocator;
379 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
377380 defer gpa.free(response);
378381
379382 var expected_response = std.array_list.Managed(u8).init(gpa);
......@@ -413,7 +416,7 @@ test "general client/server API coverage" {
413416 else => |e| return e,
414417 };
415418
416 try handleRequest(&request, net_server.listen_address.getPort());
419 try handleRequest(&request, net_server.socket.address.getPort());
417420 }
418421 }
419422 }
......@@ -543,9 +546,9 @@ test "general client/server API coverage" {
543546
544547 fn getUnusedTcpPort() !u16 {
545548 const addr = try net.IpAddress.parse("127.0.0.1", 0);
546 var s = try addr.listen(.{});
547 defer s.deinit();
548 return s.listen_address.in.getPort();
549 var s = try addr.listen(io, .{});
550 defer s.deinit(io);
551 return s.socket.address.getPort();
549552 }
550553 });
551554 defer test_server.destroy();
......@@ -553,7 +556,7 @@ test "general client/server API coverage" {
553556 const log = std.log.scoped(.client);
554557
555558 const gpa = std.testing.allocator;
556 var client: http.Client = .{ .allocator = gpa };
559 var client: http.Client = .{ .allocator = gpa, .io = io };
557560 defer client.deinit();
558561
559562 const port = test_server.port();
......@@ -918,7 +921,10 @@ test "Server streams both reading and writing" {
918921 });
919922 defer test_server.destroy();
920923
921 var client: http.Client = .{ .allocator = std.testing.allocator };
924 var client: http.Client = .{
925 .allocator = std.testing.allocator,
926 .io = io,
927 };
922928 defer client.deinit();
923929
924930 var redirect_buffer: [555]u8 = undefined;
......@@ -1089,17 +1095,20 @@ fn echoTests(client: *http.Client, port: u16) !void {
10891095}
10901096
10911097const TestServer = struct {
1098 io: Io,
10921099 shutting_down: bool,
10931100 server_thread: std.Thread,
10941101 net_server: net.Server,
10951102
10961103 fn destroy(self: *@This()) void {
1104 const io = self.io;
10971105 self.shutting_down = true;
1098 const conn = net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");
1099 conn.close();
1106 var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch
1107 @panic("shutdown failure");
1108 stream.close(io);
11001109
11011110 self.server_thread.join();
1102 self.net_server.deinit();
1111 self.net_server.deinit(io);
11031112 std.testing.allocator.destroy(self);
11041113 }
11051114
......@@ -1118,6 +1127,7 @@ fn createTestServer(io: Io, S: type) !*TestServer {
11181127 const address = try net.IpAddress.parse("127.0.0.1", 0);
11191128 const test_server = try std.testing.allocator.create(TestServer);
11201129 test_server.* = .{
1130 .io = io,
11211131 .net_server = try address.listen(io, .{ .reuse_address = true }),
11221132 .shutting_down = false,
11231133 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),