authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 21:39:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:48-07:00
log00f26cb0a4d60e908719309f4daa6598316ef74e
tree1781d40c6868aeefe7fae3942e6a2e91e58bc6b3
parent85a6fea3be48bdc8e7060eca6d81b3a8906eeff7

WIP land the std.Io interface

fix std lib compilation errors caused by introducing std.Io

11 files changed, 444 insertions(+), 3431 deletions(-)

lib/std/Build/WebServer.zig+5-4
......@@ -2,12 +2,12 @@ gpa: Allocator,
22thread_pool: *std.Thread.Pool,
33graph: *const Build.Graph,
44all_steps: []const *Build.Step,
5listen_address: std.net.Address,
5listen_address: net.IpAddress,
66ttyconf: std.Io.tty.Config,
77root_prog_node: std.Progress.Node,
88watch: bool,
99
10tcp_server: ?std.net.Server,
10tcp_server: ?net.Server,
1111serve_thread: ?std.Thread,
1212
1313base_timestamp: i128,
......@@ -56,7 +56,7 @@ pub const Options = struct {
5656 ttyconf: std.Io.tty.Config,
5757 root_prog_node: std.Progress.Node,
5858 watch: bool,
59 listen_address: std.net.Address,
59 listen_address: net.IpAddress,
6060};
6161pub fn init(opts: Options) WebServer {
6262 // The upcoming `std.Io` interface should allow us to use `Io.async` and `Io.concurrent`
......@@ -244,7 +244,7 @@ pub fn now(s: *const WebServer) i64 {
244244 return @intCast(std.time.nanoTimestamp() - s.base_timestamp);
245245}
246246
247fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
247fn accept(ws: *WebServer, connection: net.Server.Connection) void {
248248 defer connection.stream.close();
249249
250250 var send_buffer: [4096]u8 = undefined;
......@@ -851,5 +851,6 @@ const Cache = Build.Cache;
851851const Fuzz = Build.Fuzz;
852852const abi = Build.abi;
853853const http = std.http;
854const net = std.Io.net;
854855
855856const WebServer = @This();
lib/std/Io/File.zig+28-7
......@@ -1,6 +1,8 @@
11const File = @This();
22
33const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
46
57const std = @import("../std.zig");
68const Io = std.Io;
......@@ -131,6 +133,18 @@ pub const Stat = struct {
131133 }
132134};
133135
136pub fn stdout() File {
137 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdOutput else std.posix.STDOUT_FILENO };
138}
139
140pub fn stderr() File {
141 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdError else std.posix.STDERR_FILENO };
142}
143
144pub fn stdin() File {
145 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO };
146}
147
134148pub const StatError = std.posix.FStatError || Io.Cancelable;
135149
136150/// Returns `Stat` containing basic information about the `File`.
......@@ -183,6 +197,11 @@ pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
183197 return @errorCast(file.pwrite(io, buffer, -1));
184198}
185199
200pub fn writeAll(file: File, io: Io, bytes: []const u8) WriteError!void {
201 var index: usize = 0;
202 while (index < bytes.len) index += try file.write(io, bytes[index..]);
203}
204
186205pub const PWriteError = std.fs.File.PWriteError || Io.Cancelable;
187206
188207pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
......@@ -350,7 +369,7 @@ pub const Reader = struct {
350369 const io = r.io;
351370 switch (r.mode) {
352371 .positional, .positional_reading => {
353 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
372 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
354373 },
355374 .streaming, .streaming_reading => {
356375 if (std.posix.SEEK == void) {
......@@ -359,7 +378,7 @@ pub const Reader = struct {
359378 }
360379 const seek_err = r.seek_err orelse e: {
361380 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
362 setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset));
381 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
363382 return;
364383 } else |err| {
365384 r.seek_err = err;
......@@ -384,16 +403,17 @@ pub const Reader = struct {
384403 const io = r.io;
385404 switch (r.mode) {
386405 .positional, .positional_reading => {
387 setPosAdjustingBuffer(r, offset);
406 setLogicalPos(r, offset);
388407 },
389408 .streaming, .streaming_reading => {
390 if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));
409 const logical_pos = logicalPos(r);
410 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
391411 if (r.seek_err) |err| return err;
392412 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
393413 r.seek_err = err;
394414 return err;
395415 };
396 setPosAdjustingBuffer(r, offset);
416 setLogicalPos(r, offset);
397417 },
398418 .failure => return r.seek_err.?,
399419 }
......@@ -403,7 +423,7 @@ pub const Reader = struct {
403423 return r.pos - r.interface.bufferedLen();
404424 }
405425
406 fn setPosAdjustingBuffer(r: *Reader, offset: u64) void {
426 fn setLogicalPos(r: *Reader, offset: u64) void {
407427 const logical_pos = logicalPos(r);
408428 if (offset < logical_pos or offset >= r.pos) {
409429 r.interface.seek = 0;
......@@ -544,9 +564,10 @@ pub const Reader = struct {
544564 }
545565 }
546566
567 /// Returns whether the stream is at the logical end.
547568 pub fn atEnd(r: *Reader) bool {
548569 // Even if stat fails, size is set when end is encountered.
549570 const size = r.size orelse return false;
550 return size - r.pos == 0;
571 return size - logicalPos(r) == 0;
551572 }
552573};
lib/std/Io/net.zig+25-2
......@@ -43,6 +43,14 @@ pub const Protocol = enum(u32) {
4343 mptcp = 262,
4444};
4545
46/// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
47/// first release to support them.
48pub const has_unix_sockets = switch (native_os) {
49 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
50 .wasi => false,
51 else => true,
52};
53
4654pub const IpAddress = union(enum) {
4755 ip4: Ip4Address,
4856 ip6: Ip6Address,
......@@ -980,7 +988,13 @@ pub const Stream = struct {
980988 stream: Stream,
981989 err: ?Error,
982990
983 pub const Error = std.net.Stream.ReadError || Io.Cancelable || Io.Writer.Error || error{EndOfStream};
991 pub const Error = std.posix.ReadError || error{
992 SocketNotBound,
993 MessageTooBig,
994 NetworkSubsystemFailed,
995 ConnectionResetByPeer,
996 SocketUnconnected,
997 } || Io.Cancelable || Io.Writer.Error || error{EndOfStream};
984998
985999 pub fn init(stream: Stream, buffer: []u8) Reader {
9861000 return .{
......@@ -1019,7 +1033,15 @@ pub const Stream = struct {
10191033 stream: Stream,
10201034 err: ?Error = null,
10211035
1022 pub const Error = std.net.Stream.WriteError || Io.Cancelable;
1036 pub const Error = std.posix.SendMsgError || error{
1037 ConnectionResetByPeer,
1038 SocketNotBound,
1039 MessageTooBig,
1040 NetworkSubsystemFailed,
1041 SystemResources,
1042 SocketUnconnected,
1043 Unexpected,
1044 } || Io.Cancelable;
10231045
10241046 pub fn init(stream: Stream, buffer: []u8) Writer {
10251047 return .{
......@@ -1096,4 +1118,5 @@ fn testIp6ParseTransform(expected: []const u8, input: []const u8) !void {
10961118
10971119test {
10981120 _ = HostName;
1121 _ = @import("net/test.zig");
10991122}
lib/std/Io/net/test.zig created+373
......@@ -0,0 +1,373 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const net = std.Io.net;
4const mem = std.mem;
5const testing = std.testing;
6
7test "parse and render IP addresses at comptime" {
8 comptime {
9 const ipv6addr = net.IpAddress.parseIp("::1", 0) catch unreachable;
10 try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
11
12 const ipv4addr = net.IpAddress.parseIp("127.0.0.1", 0) catch unreachable;
13 try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
14
15 try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.parseIp("::123.123.123.123", 0));
16 try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.parseIp("127.01.0.1", 0));
17 try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.resolveIp("::123.123.123.123", 0));
18 try testing.expectError(error.InvalidIpAddressFormat, net.IpAddress.resolveIp("127.01.0.1", 0));
19 }
20}
21
22test "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});
25}
26
27test "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),
30 });
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),
33 });
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),
36 });
37}
38
39test "parse IPv6 address, check raw bytes" {
40 const expected_raw: [16]u8 = .{
41 0x20, 0x01, 0x0d, 0xb8, // 2001:db8
42 0x00, 0x00, 0x00, 0x00, // :0000:0000
43 0x00, 0x01, 0x00, 0x00, // :0001:0000
44 0x00, 0x00, 0x00, 0x02, // :0000:0002
45 };
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);
51}
52
53test "parse and render IPv6 addresses" {
54 var buffer: [100]u8 = undefined;
55 const ips = [_][]const u8{
56 "FF01:0:0:0:0:0:0:FB",
57 "FF01::Fb",
58 "::1",
59 "::",
60 "1::",
61 "2001:db8::",
62 "::1234:5678",
63 "2001:db8::1234:5678",
64 "FF01::FB%1234",
65 "::ffff:123.5.123.5",
66 };
67 const printed = [_][]const u8{
68 "ff01::fb",
69 "ff01::fb",
70 "::1",
71 "::",
72 "1::",
73 "2001:db8::",
74 "::1234:5678",
75 "2001:db8::1234:5678",
76 "ff01::fb%1234",
77 "::ffff:123.5.123.5",
78 };
79 for (ips, 0..) |ip, i| {
80 const addr = net.IpAddress.parseIp6(ip, 0) catch unreachable;
81 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]));
83
84 if (builtin.os.tag == .linux) {
85 const addr_via_resolve = net.IpAddress.resolveIp6(ip, 0) catch unreachable;
86 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]));
88 }
89 }
90
91 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6(":::", 0));
92 try testing.expectError(error.Overflow, net.IpAddress.parseIp6("FF001::FB", 0));
93 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6("FF01::Fb:zig", 0));
94 try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
95 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("FF01:", 0));
96 try testing.expectError(error.InvalidIpv4Mapping, net.IpAddress.parseIp6("::123.123.123.123", 0));
97 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0));
98 // TODO Make this test pass on other operating systems.
99 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));
101 // 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));
104 }
105}
106
107test "invalid but parseable IPv6 scope ids" {
108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
110 // TODO Make this test pass on other operating systems.
111 return error.SkipZigTest;
112 }
113
114 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6("ff01::fb%123s45678901234", 0));
115}
116
117test "parse and render IPv4 addresses" {
118 var buffer: [18]u8 = undefined;
119 for ([_][]const u8{
120 "0.0.0.0",
121 "255.255.255.255",
122 "1.2.3.4",
123 "123.255.0.91",
124 "127.0.0.1",
125 }) |ip| {
126 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;
127 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]));
129 }
130
131 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
132 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0));
133 try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp4("127.0.0.1.1", 0));
134 try testing.expectError(error.Incomplete, net.IpAddress.parseIp4("127.0.0.", 0));
135 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("100..0.1", 0));
136 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));
137}
138
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
150test "resolve DNS" {
151 if (builtin.os.tag == .wasi) return error.SkipZigTest;
152
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 }
161
162 // Resolve localhost, this should not fail.
163 {
164 const localhost_v4 = try net.IpAddress.parseIp("127.0.0.1", 80);
165 const localhost_v6 = try net.IpAddress.parseIp("::2", 80);
166
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;
171 } else @panic("unexpected address for localhost");
172 }
173
174 {
175 // The tests are required to work even when there is no Internet connection,
176 // 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) {
178 error.UnknownHostName => return error.SkipZigTest,
179 error.TemporaryNameServerFailure => return error.SkipZigTest,
180 else => return err,
181 };
182 result.deinit();
183 }
184}
185
186test "listen on a port, send bytes, receive bytes" {
187 if (builtin.single_threaded) return error.SkipZigTest;
188 if (builtin.os.tag == .wasi) return error.SkipZigTest;
189
190 if (builtin.os.tag == .windows) {
191 _ = try std.os.windows.WSAStartup(2, 2);
192 }
193 defer {
194 if (builtin.os.tag == .windows) {
195 std.os.windows.WSACleanup() catch unreachable;
196 }
197 }
198
199 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
200 // configured.
201 const localhost = try net.IpAddress.parseIp("127.0.0.1", 0);
202
203 var server = try localhost.listen(.{});
204 defer server.deinit();
205
206 const S = struct {
207 fn clientFn(server_address: net.IpAddress) !void {
208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();
210
211 var stream_writer = socket.writer(&.{});
212 try stream_writer.interface.writeAll("Hello world!");
213 }
214 };
215
216 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
217 defer t.join();
218
219 var client = try server.accept();
220 defer client.stream.close();
221 var buf: [16]u8 = undefined;
222 var stream_reader = client.stream.reader(&.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);
224
225 try testing.expectEqual(@as(usize, 12), n);
226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
227}
228
229test "listen on an in use port" {
230 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
231 // TODO build abstractions for other operating systems
232 return error.SkipZigTest;
233 }
234
235 const localhost = try net.IpAddress.parseIp("127.0.0.1", 0);
236
237 var server1 = try localhost.listen(.{ .reuse_address = true });
238 defer server1.deinit();
239
240 var server2 = try server1.listen_address.listen(.{ .reuse_address = true });
241 defer server2.deinit();
242}
243
244fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
245 if (builtin.os.tag == .wasi) return error.SkipZigTest;
246
247 const connection = try net.tcpConnectToHost(allocator, name, port);
248 defer connection.close();
249
250 var buf: [100]u8 = undefined;
251 const len = try connection.read(&buf);
252 const msg = buf[0..len];
253 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
254}
255
256fn testClient(addr: net.IpAddress) anyerror!void {
257 if (builtin.os.tag == .wasi) return error.SkipZigTest;
258
259 const socket_file = try net.tcpConnectToAddress(addr);
260 defer socket_file.close();
261
262 var buf: [100]u8 = undefined;
263 const len = try socket_file.read(&buf);
264 const msg = buf[0..len];
265 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
266}
267
268fn testServer(server: *net.Server) anyerror!void {
269 if (builtin.os.tag == .wasi) return error.SkipZigTest;
270
271 var client = try server.accept();
272
273 const stream = client.stream.writer();
274 try stream.print("hello from server\n", .{});
275}
276
277test "listen on a unix socket, send bytes, receive bytes" {
278 if (builtin.single_threaded) return error.SkipZigTest;
279 if (!net.has_unix_sockets) return error.SkipZigTest;
280
281 if (builtin.os.tag == .windows) {
282 _ = try std.os.windows.WSAStartup(2, 2);
283 }
284 defer {
285 if (builtin.os.tag == .windows) {
286 std.os.windows.WSACleanup() catch unreachable;
287 }
288 }
289
290 const socket_path = try generateFileName("socket.unix");
291 defer testing.allocator.free(socket_path);
292
293 const socket_addr = try net.IpAddress.initUnix(socket_path);
294 defer std.fs.cwd().deleteFile(socket_path) catch {};
295
296 var server = try socket_addr.listen(.{});
297 defer server.deinit();
298
299 const S = struct {
300 fn clientFn(path: []const u8) !void {
301 const socket = try net.connectUnixSocket(path);
302 defer socket.close();
303
304 var stream_writer = socket.writer(&.{});
305 try stream_writer.interface.writeAll("Hello world!");
306 }
307 };
308
309 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
310 defer t.join();
311
312 var client = try server.accept();
313 defer client.stream.close();
314 var buf: [16]u8 = undefined;
315 var stream_reader = client.stream.reader(&.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);
317
318 try testing.expectEqual(@as(usize, 12), n);
319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
320}
321
322test "listen on a unix socket with reuse_address option" {
323 if (!net.has_unix_sockets) return error.SkipZigTest;
324 // Windows doesn't implement reuse port option.
325 if (builtin.os.tag == .windows) return error.SkipZigTest;
326
327 const socket_path = try generateFileName("socket.unix");
328 defer testing.allocator.free(socket_path);
329
330 const socket_addr = try net.Address.initUnix(socket_path);
331 defer std.fs.cwd().deleteFile(socket_path) catch {};
332
333 var server = try socket_addr.listen(.{ .reuse_address = true });
334 server.deinit();
335}
336
337fn generateFileName(base_name: []const u8) ![]const u8 {
338 const random_bytes_count = 12;
339 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
340 var random_bytes: [12]u8 = undefined;
341 std.crypto.random.bytes(&random_bytes);
342 var sub_path: [sub_path_len]u8 = undefined;
343 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
344 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
345}
346
347test "non-blocking tcp server" {
348 if (builtin.os.tag == .wasi) return error.SkipZigTest;
349 if (true) {
350 // https://github.com/ziglang/zig/issues/18315
351 return error.SkipZigTest;
352 }
353
354 const localhost = try net.IpAddress.parseIp("127.0.0.1", 0);
355 var server = localhost.listen(.{ .force_nonblocking = true });
356 defer server.deinit();
357
358 const accept_err = server.accept();
359 try testing.expectError(error.WouldBlock, accept_err);
360
361 const socket_file = try net.tcpConnectToAddress(server.listen_address);
362 defer socket_file.close();
363
364 var client = try server.accept();
365 defer client.stream.close();
366 const stream = client.stream.writer();
367 try stream.print("hello from server\n", .{});
368
369 var buf: [100]u8 = undefined;
370 const len = try socket_file.read(&buf);
371 const msg = buf[0..len];
372 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
373}
lib/std/Progress.zig+1-3
......@@ -523,9 +523,7 @@ pub fn setStatus(new_status: Status) void {
523523
524524/// Returns whether a resize is needed to learn the terminal size.
525525fn wait(timeout_ns: u64) bool {
526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_|
527 true
528 else |err| switch (err) {
526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) {
529527 error.Timeout => false,
530528 };
531529 global_progress.redraw_event.reset();
lib/std/Thread.zig+10-11
......@@ -71,7 +71,7 @@ pub const ResetEvent = enum(u32) {
7171 ///
7272 /// The memory accesses before the set() can be said to happen before
7373 /// timedWait() returns without error.
74 pub fn timedWait(re: *ResetEvent, timeout_ns: u64) void {
74 pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void {
7575 if (builtin.single_threaded) switch (re.*) {
7676 .unset => {
7777 sleep(timeout_ns);
......@@ -1774,9 +1774,9 @@ test "setName, getName" {
17741774 if (builtin.single_threaded) return error.SkipZigTest;
17751775
17761776 const Context = struct {
1777 start_wait_event: ResetEvent = .{},
1778 test_done_event: ResetEvent = .{},
1779 thread_done_event: ResetEvent = .{},
1777 start_wait_event: ResetEvent = .unset,
1778 test_done_event: ResetEvent = .unset,
1779 thread_done_event: ResetEvent = .unset,
17801780
17811781 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
17821782 thread: Thread = undefined,
......@@ -1843,7 +1843,7 @@ test join {
18431843 if (builtin.single_threaded) return error.SkipZigTest;
18441844
18451845 var value: usize = 0;
1846 var event = ResetEvent{};
1846 var event: ResetEvent = .unset;
18471847
18481848 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
18491849 thread.join();
......@@ -1855,7 +1855,7 @@ test detach {
18551855 if (builtin.single_threaded) return error.SkipZigTest;
18561856
18571857 var value: usize = 0;
1858 var event = ResetEvent{};
1858 var event: ResetEvent = .unset;
18591859
18601860 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
18611861 thread.detach();
......@@ -1902,8 +1902,7 @@ fn testTls() !void {
19021902}
19031903
19041904test "ResetEvent smoke test" {
1905 // make sure the event is unset
1906 var event = ResetEvent{};
1905 var event: ResetEvent = .unset;
19071906 try testing.expectEqual(false, event.isSet());
19081907
19091908 // make sure the event gets set
......@@ -1932,8 +1931,8 @@ test "ResetEvent signaling" {
19321931 }
19331932
19341933 const Context = struct {
1935 in: ResetEvent = .{},
1936 out: ResetEvent = .{},
1934 in: ResetEvent = .unset,
1935 out: ResetEvent = .unset,
19371936 value: usize = 0,
19381937
19391938 fn input(self: *@This()) !void {
......@@ -1994,7 +1993,7 @@ test "ResetEvent broadcast" {
19941993
19951994 const num_threads = 10;
19961995 const Barrier = struct {
1997 event: ResetEvent = .{},
1996 event: ResetEvent = .unset,
19981997 counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads),
19991998
20001999 fn wait(self: *@This()) void {
lib/std/fs.zig-19
......@@ -97,25 +97,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
9797/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
9898pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
9999
100/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
101/// are absolute. See `Dir.updateFile` for a function that operates on both
102/// absolute and relative paths.
103/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
104/// On WASI, both paths should be encoded as valid UTF-8.
105/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
106pub fn updateFileAbsolute(
107 source_path: []const u8,
108 dest_path: []const u8,
109 args: Dir.CopyFileOptions,
110) !std.Io.Dir.PrevStatus {
111 assert(path.isAbsolute(source_path));
112 assert(path.isAbsolute(dest_path));
113 const my_cwd = cwd();
114 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
115}
116
117test updateFileAbsolute {}
118
119100/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
120101/// are absolute. See `Dir.copyFile` for a function that operates on both
121102/// absolute and relative paths.
lib/std/fs/File.zig+2-587
......@@ -698,17 +698,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {
698698 return posix.read(self.handle, buffer);
699699}
700700
701/// Deprecated in favor of `Reader`.
702pub fn readAll(self: File, buffer: []u8) ReadError!usize {
703 var index: usize = 0;
704 while (index != buffer.len) {
705 const amt = try self.read(buffer[index..]);
706 if (amt == 0) break;
707 index += amt;
708 }
709 return index;
710}
711
712701/// On Windows, this function currently does alter the file pointer.
713702/// https://github.com/ziglang/zig/issues/12783
714703pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
......@@ -719,17 +708,6 @@ pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
719708 return posix.pread(self.handle, buffer, offset);
720709}
721710
722/// Deprecated in favor of `Reader`.
723pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
724 var index: usize = 0;
725 while (index != buffer.len) {
726 const amt = try self.pread(buffer[index..], offset + index);
727 if (amt == 0) break;
728 index += amt;
729 }
730 return index;
731}
732
733711/// See https://github.com/ziglang/zig/issues/7699
734712pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
735713 if (is_windows) {
......@@ -741,36 +719,6 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
741719 return posix.readv(self.handle, iovecs);
742720}
743721
744/// Deprecated in favor of `Reader`.
745pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
746 if (iovecs.len == 0) return 0;
747
748 // We use the address of this local variable for all zero-length
749 // vectors so that the OS does not complain that we are giving it
750 // addresses outside the application's address space.
751 var garbage: [1]u8 = undefined;
752 for (iovecs) |*v| {
753 if (v.len == 0) v.base = &garbage;
754 }
755
756 var i: usize = 0;
757 var off: usize = 0;
758 while (true) {
759 var amt = try self.readv(iovecs[i..]);
760 var eof = amt == 0;
761 off += amt;
762 while (amt >= iovecs[i].len) {
763 amt -= iovecs[i].len;
764 i += 1;
765 if (i >= iovecs.len) return off;
766 eof = false;
767 }
768 if (eof) return off;
769 iovecs[i].base += amt;
770 iovecs[i].len -= amt;
771 }
772}
773
774722/// See https://github.com/ziglang/zig/issues/7699
775723/// On Windows, this function currently does alter the file pointer.
776724/// https://github.com/ziglang/zig/issues/12783
......@@ -784,28 +732,6 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u
784732 return posix.preadv(self.handle, iovecs, offset);
785733}
786734
787/// Deprecated in favor of `Reader`.
788pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
789 if (iovecs.len == 0) return 0;
790
791 var i: usize = 0;
792 var off: usize = 0;
793 while (true) {
794 var amt = try self.preadv(iovecs[i..], offset + off);
795 var eof = amt == 0;
796 off += amt;
797 while (amt >= iovecs[i].len) {
798 amt -= iovecs[i].len;
799 i += 1;
800 if (i >= iovecs.len) return off;
801 eof = false;
802 }
803 if (eof) return off;
804 iovecs[i].base += amt;
805 iovecs[i].len -= amt;
806 }
807}
808
809735pub const WriteError = posix.WriteError;
810736pub const PWriteError = posix.PWriteError;
811737
......@@ -817,7 +743,6 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {
817743 return posix.write(self.handle, bytes);
818744}
819745
820/// Deprecated in favor of `Writer`.
821746pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
822747 var index: usize = 0;
823748 while (index < bytes.len) {
......@@ -835,14 +760,6 @@ pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
835760 return posix.pwrite(self.handle, bytes, offset);
836761}
837762
838/// Deprecated in favor of `Writer`.
839pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
840 var index: usize = 0;
841 while (index < bytes.len) {
842 index += try self.pwrite(bytes[index..], offset + index);
843 }
844}
845
846763/// See https://github.com/ziglang/zig/issues/7699
847764pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
848765 if (is_windows) {
......@@ -855,31 +772,6 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
855772 return posix.writev(self.handle, iovecs);
856773}
857774
858/// Deprecated in favor of `Writer`.
859pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
860 if (iovecs.len == 0) return;
861
862 // We use the address of this local variable for all zero-length
863 // vectors so that the OS does not complain that we are giving it
864 // addresses outside the application's address space.
865 var garbage: [1]u8 = undefined;
866 for (iovecs) |*v| {
867 if (v.len == 0) v.base = &garbage;
868 }
869
870 var i: usize = 0;
871 while (true) {
872 var amt = try self.writev(iovecs[i..]);
873 while (amt >= iovecs[i].len) {
874 amt -= iovecs[i].len;
875 i += 1;
876 if (i >= iovecs.len) return;
877 }
878 iovecs[i].base += amt;
879 iovecs[i].len -= amt;
880 }
881}
882
883775/// See https://github.com/ziglang/zig/issues/7699
884776/// On Windows, this function currently does alter the file pointer.
885777/// https://github.com/ziglang/zig/issues/12783
......@@ -893,485 +785,8 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError
893785 return posix.pwritev(self.handle, iovecs, offset);
894786}
895787
896/// Deprecated in favor of `Writer`.
897pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
898 if (iovecs.len == 0) return;
899 var i: usize = 0;
900 var off: u64 = 0;
901 while (true) {
902 var amt = try self.pwritev(iovecs[i..], offset + off);
903 off += amt;
904 while (amt >= iovecs[i].len) {
905 amt -= iovecs[i].len;
906 i += 1;
907 if (i >= iovecs.len) return;
908 }
909 iovecs[i].base += amt;
910 iovecs[i].len -= amt;
911 }
912}
913
914pub const CopyRangeError = posix.CopyFileRangeError;
915
916/// Deprecated in favor of `Writer`.
917pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
918 const adjusted_len = math.cast(usize, len) orelse maxInt(usize);
919 const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
920 return result;
921}
922
923/// Deprecated in favor of `Writer`.
924pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
925 var total_bytes_copied: u64 = 0;
926 var in_off = in_offset;
927 var out_off = out_offset;
928 while (total_bytes_copied < len) {
929 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
930 if (amt_copied == 0) return total_bytes_copied;
931 total_bytes_copied += amt_copied;
932 in_off += amt_copied;
933 out_off += amt_copied;
934 }
935 return total_bytes_copied;
936}
937
938/// Memoizes key information about a file handle such as:
939/// * The size from calling stat, or the error that occurred therein.
940/// * The current seek position.
941/// * The error that occurred when trying to seek.
942/// * Whether reading should be done positionally or streaming.
943/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
944/// versus plain variants (e.g. `read`).
945///
946/// Fulfills the `std.Io.Reader` interface.
947pub const Reader = struct {
948 file: File,
949 err: ?ReadError = null,
950 mode: Reader.Mode = .positional,
951 /// Tracks the true seek position in the file. To obtain the logical
952 /// position, use `logicalPos`.
953 pos: u64 = 0,
954 size: ?u64 = null,
955 size_err: ?SizeError = null,
956 seek_err: ?Reader.SeekError = null,
957 interface: std.Io.Reader,
958
959 pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
960 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
961 Streaming,
962 };
963
964 pub const SeekError = File.SeekError || error{
965 /// Seeking fell back to reading, and reached the end before the requested seek position.
966 /// `pos` remains at the end of the file.
967 EndOfStream,
968 /// Seeking fell back to reading, which failed.
969 ReadFailed,
970 };
971
972 pub const Mode = enum {
973 streaming,
974 positional,
975 /// Avoid syscalls other than `read` and `readv`.
976 streaming_reading,
977 /// Avoid syscalls other than `pread` and `preadv`.
978 positional_reading,
979 /// Indicates reading cannot continue because of a seek failure.
980 failure,
981
982 pub fn toStreaming(m: @This()) @This() {
983 return switch (m) {
984 .positional, .streaming => .streaming,
985 .positional_reading, .streaming_reading => .streaming_reading,
986 .failure => .failure,
987 };
988 }
989
990 pub fn toReading(m: @This()) @This() {
991 return switch (m) {
992 .positional, .positional_reading => .positional_reading,
993 .streaming, .streaming_reading => .streaming_reading,
994 .failure => .failure,
995 };
996 }
997 };
998
999 pub fn initInterface(buffer: []u8) std.Io.Reader {
1000 return .{
1001 .vtable = &.{
1002 .stream = Reader.stream,
1003 .discard = Reader.discard,
1004 .readVec = Reader.readVec,
1005 },
1006 .buffer = buffer,
1007 .seek = 0,
1008 .end = 0,
1009 };
1010 }
1011
1012 pub fn init(file: File, buffer: []u8) Reader {
1013 return .{
1014 .file = file,
1015 .interface = initInterface(buffer),
1016 };
1017 }
1018
1019 pub fn initSize(file: File, buffer: []u8, size: ?u64) Reader {
1020 return .{
1021 .file = file,
1022 .interface = initInterface(buffer),
1023 .size = size,
1024 };
1025 }
1026
1027 /// Positional is more threadsafe, since the global seek position is not
1028 /// affected, but when such syscalls are not available, preemptively
1029 /// initializing in streaming mode skips a failed syscall.
1030 pub fn initStreaming(file: File, buffer: []u8) Reader {
1031 return .{
1032 .file = file,
1033 .interface = Reader.initInterface(buffer),
1034 .mode = .streaming,
1035 .seek_err = error.Unseekable,
1036 .size_err = error.Streaming,
1037 };
1038 }
1039
1040 pub fn getSize(r: *Reader) SizeError!u64 {
1041 return r.size orelse {
1042 if (r.size_err) |err| return err;
1043 if (is_windows) {
1044 if (windows.GetFileSizeEx(r.file.handle)) |size| {
1045 r.size = size;
1046 return size;
1047 } else |err| {
1048 r.size_err = err;
1049 return err;
1050 }
1051 }
1052 if (posix.Stat == void) {
1053 r.size_err = error.Streaming;
1054 return error.Streaming;
1055 }
1056 if (stat(r.file)) |st| {
1057 if (st.kind == .file) {
1058 r.size = st.size;
1059 return st.size;
1060 } else {
1061 r.mode = r.mode.toStreaming();
1062 r.size_err = error.Streaming;
1063 return error.Streaming;
1064 }
1065 } else |err| {
1066 r.size_err = err;
1067 return err;
1068 }
1069 };
1070 }
1071
1072 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
1073 switch (r.mode) {
1074 .positional, .positional_reading => {
1075 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1076 },
1077 .streaming, .streaming_reading => {
1078 if (posix.SEEK == void) {
1079 r.seek_err = error.Unseekable;
1080 return error.Unseekable;
1081 }
1082 const seek_err = r.seek_err orelse e: {
1083 if (posix.lseek_CUR(r.file.handle, offset)) |_| {
1084 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1085 return;
1086 } else |err| {
1087 r.seek_err = err;
1088 break :e err;
1089 }
1090 };
1091 var remaining = std.math.cast(u64, offset) orelse return seek_err;
1092 while (remaining > 0) {
1093 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
1094 r.seek_err = err;
1095 return err;
1096 };
1097 }
1098 r.interface.seek = 0;
1099 r.interface.end = 0;
1100 },
1101 .failure => return r.seek_err.?,
1102 }
1103 }
1104
1105 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
1106 switch (r.mode) {
1107 .positional, .positional_reading => {
1108 setLogicalPos(r, offset);
1109 },
1110 .streaming, .streaming_reading => {
1111 const logical_pos = logicalPos(r);
1112 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
1113 if (r.seek_err) |err| return err;
1114 posix.lseek_SET(r.file.handle, offset) catch |err| {
1115 r.seek_err = err;
1116 return err;
1117 };
1118 setLogicalPos(r, offset);
1119 },
1120 .failure => return r.seek_err.?,
1121 }
1122 }
1123
1124 pub fn logicalPos(r: *const Reader) u64 {
1125 return r.pos - r.interface.bufferedLen();
1126 }
1127
1128 fn setLogicalPos(r: *Reader, offset: u64) void {
1129 const logical_pos = logicalPos(r);
1130 if (offset < logical_pos or offset >= r.pos) {
1131 r.interface.seek = 0;
1132 r.interface.end = 0;
1133 r.pos = offset;
1134 } else {
1135 const logical_delta: usize = @intCast(offset - logical_pos);
1136 r.interface.seek += logical_delta;
1137 }
1138 }
1139
1140 /// Number of slices to store on the stack, when trying to send as many byte
1141 /// vectors through the underlying read calls as possible.
1142 const max_buffers_len = 16;
1143
1144 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1145 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1146 switch (r.mode) {
1147 .positional, .streaming => @panic("TODO"),
1148 .positional_reading => {
1149 const dest = limit.slice(try w.writableSliceGreedy(1));
1150 var data: [1][]u8 = .{dest};
1151 const n = try readVecPositional(r, &data);
1152 w.advance(n);
1153 return n;
1154 },
1155 .streaming_reading => {
1156 const dest = limit.slice(try w.writableSliceGreedy(1));
1157 var data: [1][]u8 = .{dest};
1158 const n = try readVecStreaming(r, &data);
1159 w.advance(n);
1160 return n;
1161 },
1162 .failure => return error.ReadFailed,
1163 }
1164 }
1165
1166 fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize {
1167 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1168 switch (r.mode) {
1169 .positional, .positional_reading => return readVecPositional(r, data),
1170 .streaming, .streaming_reading => return readVecStreaming(r, data),
1171 .failure => return error.ReadFailed,
1172 }
1173 }
1174
1175 fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1176 const io_reader = &r.interface;
1177 if (is_windows) {
1178 // Unfortunately, `ReadFileScatter` cannot be used since it
1179 // requires page alignment.
1180 if (io_reader.seek == io_reader.end) {
1181 io_reader.seek = 0;
1182 io_reader.end = 0;
1183 }
1184 const first = data[0];
1185 if (first.len >= io_reader.buffer.len - io_reader.end) {
1186 return readPositional(r, first);
1187 } else {
1188 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);
1189 return 0;
1190 }
1191 }
1192 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1193 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1194 const dest = iovecs_buffer[0..dest_n];
1195 assert(dest[0].len > 0);
1196 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {
1197 error.Unseekable => {
1198 r.mode = r.mode.toStreaming();
1199 const pos = r.pos;
1200 if (pos != 0) {
1201 r.pos = 0;
1202 r.seekBy(@intCast(pos)) catch {
1203 r.mode = .failure;
1204 return error.ReadFailed;
1205 };
1206 }
1207 return 0;
1208 },
1209 else => |e| {
1210 r.err = e;
1211 return error.ReadFailed;
1212 },
1213 };
1214 if (n == 0) {
1215 r.size = r.pos;
1216 return error.EndOfStream;
1217 }
1218 r.pos += n;
1219 if (n > data_size) {
1220 io_reader.end += n - data_size;
1221 return data_size;
1222 }
1223 return n;
1224 }
1225
1226 fn readVecStreaming(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1227 const io_reader = &r.interface;
1228 if (is_windows) {
1229 // Unfortunately, `ReadFileScatter` cannot be used since it
1230 // requires page alignment.
1231 if (io_reader.seek == io_reader.end) {
1232 io_reader.seek = 0;
1233 io_reader.end = 0;
1234 }
1235 const first = data[0];
1236 if (first.len >= io_reader.buffer.len - io_reader.end) {
1237 return readStreaming(r, first);
1238 } else {
1239 io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]);
1240 return 0;
1241 }
1242 }
1243 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1244 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1245 const dest = iovecs_buffer[0..dest_n];
1246 assert(dest[0].len > 0);
1247 const n = posix.readv(r.file.handle, dest) catch |err| {
1248 r.err = err;
1249 return error.ReadFailed;
1250 };
1251 if (n == 0) {
1252 r.size = r.pos;
1253 return error.EndOfStream;
1254 }
1255 r.pos += n;
1256 if (n > data_size) {
1257 io_reader.end += n - data_size;
1258 return data_size;
1259 }
1260 return n;
1261 }
1262
1263 fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
1264 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1265 const file = r.file;
1266 const pos = r.pos;
1267 switch (r.mode) {
1268 .positional, .positional_reading => {
1269 const size = r.getSize() catch {
1270 r.mode = r.mode.toStreaming();
1271 return 0;
1272 };
1273 const delta = @min(@intFromEnum(limit), size - pos);
1274 r.pos = pos + delta;
1275 return delta;
1276 },
1277 .streaming, .streaming_reading => {
1278 // Unfortunately we can't seek forward without knowing the
1279 // size because the seek syscalls provided to us will not
1280 // return the true end position if a seek would exceed the
1281 // end.
1282 fallback: {
1283 if (r.size_err == null and r.seek_err == null) break :fallback;
1284 var trash_buffer: [128]u8 = undefined;
1285 if (is_windows) {
1286 const n = windows.ReadFile(file.handle, limit.slice(&trash_buffer), null) catch |err| {
1287 r.err = err;
1288 return error.ReadFailed;
1289 };
1290 if (n == 0) {
1291 r.size = pos;
1292 return error.EndOfStream;
1293 }
1294 r.pos = pos + n;
1295 return n;
1296 }
1297 var iovecs: [max_buffers_len]std.posix.iovec = undefined;
1298 var iovecs_i: usize = 0;
1299 var remaining = @intFromEnum(limit);
1300 while (remaining > 0 and iovecs_i < iovecs.len) {
1301 iovecs[iovecs_i] = .{ .base = &trash_buffer, .len = @min(trash_buffer.len, remaining) };
1302 remaining -= iovecs[iovecs_i].len;
1303 iovecs_i += 1;
1304 }
1305 const n = posix.readv(file.handle, iovecs[0..iovecs_i]) catch |err| {
1306 r.err = err;
1307 return error.ReadFailed;
1308 };
1309 if (n == 0) {
1310 r.size = pos;
1311 return error.EndOfStream;
1312 }
1313 r.pos = pos + n;
1314 return n;
1315 }
1316 const size = r.getSize() catch return 0;
1317 const n = @min(size - pos, maxInt(i64), @intFromEnum(limit));
1318 file.seekBy(n) catch |err| {
1319 r.seek_err = err;
1320 return 0;
1321 };
1322 r.pos = pos + n;
1323 return n;
1324 },
1325 .failure => return error.ReadFailed,
1326 }
1327 }
1328
1329 fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1330 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
1331 error.Unseekable => {
1332 r.mode = r.mode.toStreaming();
1333 const pos = r.pos;
1334 if (pos != 0) {
1335 r.pos = 0;
1336 r.seekBy(@intCast(pos)) catch {
1337 r.mode = .failure;
1338 return error.ReadFailed;
1339 };
1340 }
1341 return 0;
1342 },
1343 else => |e| {
1344 r.err = e;
1345 return error.ReadFailed;
1346 },
1347 };
1348 if (n == 0) {
1349 r.size = r.pos;
1350 return error.EndOfStream;
1351 }
1352 r.pos += n;
1353 return n;
1354 }
1355
1356 fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1357 const n = r.file.read(dest) catch |err| {
1358 r.err = err;
1359 return error.ReadFailed;
1360 };
1361 if (n == 0) {
1362 r.size = r.pos;
1363 return error.EndOfStream;
1364 }
1365 r.pos += n;
1366 return n;
1367 }
1368
1369 pub fn atEnd(r: *Reader) bool {
1370 // Even if stat fails, size is set when end is encountered.
1371 const size = r.size orelse return false;
1372 return size - r.pos == 0;
1373 }
1374};
788/// Deprecated in favor of `std.Io.File.Reader`.
789pub const Reader = std.Io.File.Reader;
1375790
1376791pub const Writer = struct {
1377792 file: File,
lib/std/net.zig deleted-2424
......@@ -1,2424 +0,0 @@
1//! Cross-platform networking abstractions.
2
3const std = @import("std.zig");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6const net = @This();
7const mem = std.mem;
8const posix = std.posix;
9const fs = std.fs;
10const Io = std.Io;
11const native_endian = builtin.target.cpu.arch.endian();
12const native_os = builtin.os.tag;
13const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
17
18// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
19// first release to support them.
20pub const has_unix_sockets = switch (native_os) {
21 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
22 .wasi => false,
23 else => true,
24};
25
26pub const IPParseError = error{
27 Overflow,
28 InvalidEnd,
29 InvalidCharacter,
30 Incomplete,
31};
32
33pub const IPv4ParseError = IPParseError || error{NonCanonical};
34
35pub const IPv6ParseError = IPParseError || error{InvalidIpv4Mapping};
36pub const IPv6InterfaceError = posix.SocketError || posix.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong};
37pub const IPv6ResolveError = IPv6ParseError || IPv6InterfaceError;
38
39pub const Address = extern union {
40 any: posix.sockaddr,
41 in: Ip4Address,
42 in6: Ip6Address,
43 un: if (has_unix_sockets) posix.sockaddr.un else void,
44
45 /// Parse an IP address which may include a port. For IPv4, this is just written `address:port`.
46 /// For IPv6, RFC 3986 defines this as an "IP literal", and the port is differentiated from the
47 /// address by surrounding the address part in brackets '[addr]:port'. Even if the port is not
48 /// given, the brackets are mandatory.
49 pub fn parseIpAndPort(str: []const u8) error{ InvalidAddress, InvalidPort }!Address {
50 if (str.len == 0) return error.InvalidAddress;
51 if (str[0] == '[') {
52 const addr_end = std.mem.indexOfScalar(u8, str, ']') orelse
53 return error.InvalidAddress;
54 const addr_str = str[1..addr_end];
55 const port: u16 = p: {
56 if (addr_end == str.len - 1) break :p 0;
57 if (str[addr_end + 1] != ':') return error.InvalidAddress;
58 break :p parsePort(str[addr_end + 2 ..]) orelse return error.InvalidPort;
59 };
60 return parseIp6(addr_str, port) catch error.InvalidAddress;
61 } else {
62 if (std.mem.indexOfScalar(u8, str, ':')) |idx| {
63 // hold off on `error.InvalidPort` since `error.InvalidAddress` might make more sense
64 const port: ?u16 = parsePort(str[idx + 1 ..]);
65 const addr = parseIp4(str[0..idx], port orelse 0) catch return error.InvalidAddress;
66 if (port == null) return error.InvalidPort;
67 return addr;
68 } else {
69 return parseIp4(str, 0) catch error.InvalidAddress;
70 }
71 }
72 }
73 fn parsePort(str: []const u8) ?u16 {
74 var p: u16 = 0;
75 for (str) |c| switch (c) {
76 '0'...'9' => {
77 const shifted = std.math.mul(u16, p, 10) catch return null;
78 p = std.math.add(u16, shifted, c - '0') catch return null;
79 },
80 else => return null,
81 };
82 if (p == 0) return null;
83 return p;
84 }
85
86 /// Parse the given IP address string into an Address value.
87 /// It is recommended to use `resolveIp` instead, to handle
88 /// IPv6 link-local unix addresses.
89 pub fn parseIp(name: []const u8, port: u16) !Address {
90 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
91 error.Overflow,
92 error.InvalidEnd,
93 error.InvalidCharacter,
94 error.Incomplete,
95 error.NonCanonical,
96 => {},
97 }
98
99 if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
100 error.Overflow,
101 error.InvalidEnd,
102 error.InvalidCharacter,
103 error.Incomplete,
104 error.InvalidIpv4Mapping,
105 => {},
106 }
107
108 return error.InvalidIpAddressFormat;
109 }
110
111 pub fn resolveIp(name: []const u8, port: u16) !Address {
112 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
113 error.Overflow,
114 error.InvalidEnd,
115 error.InvalidCharacter,
116 error.Incomplete,
117 error.NonCanonical,
118 => {},
119 }
120
121 if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
122 error.Overflow,
123 error.InvalidEnd,
124 error.InvalidCharacter,
125 error.Incomplete,
126 error.InvalidIpv4Mapping,
127 => {},
128 else => return err,
129 }
130
131 return error.InvalidIpAddressFormat;
132 }
133
134 pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address {
135 switch (family) {
136 posix.AF.INET => return parseIp4(name, port),
137 posix.AF.INET6 => return parseIp6(name, port),
138 posix.AF.UNSPEC => return parseIp(name, port),
139 else => unreachable,
140 }
141 }
142
143 pub fn parseIp6(buf: []const u8, port: u16) IPv6ParseError!Address {
144 return .{ .in6 = try Ip6Address.parse(buf, port) };
145 }
146
147 pub fn resolveIp6(buf: []const u8, port: u16) IPv6ResolveError!Address {
148 return .{ .in6 = try Ip6Address.resolve(buf, port) };
149 }
150
151 pub fn parseIp4(buf: []const u8, port: u16) IPv4ParseError!Address {
152 return .{ .in = try Ip4Address.parse(buf, port) };
153 }
154
155 pub fn initIp4(addr: [4]u8, port: u16) Address {
156 return .{ .in = Ip4Address.init(addr, port) };
157 }
158
159 pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
160 return .{ .in6 = Ip6Address.init(addr, port, flowinfo, scope_id) };
161 }
162
163 pub fn initUnix(path: []const u8) !Address {
164 var sock_addr = posix.sockaddr.un{
165 .family = posix.AF.UNIX,
166 .path = undefined,
167 };
168
169 // Add 1 to ensure a terminating 0 is present in the path array for maximum portability.
170 if (path.len + 1 > sock_addr.path.len) return error.NameTooLong;
171
172 @memset(&sock_addr.path, 0);
173 @memcpy(sock_addr.path[0..path.len], path);
174
175 return .{ .un = sock_addr };
176 }
177
178 /// Returns the port in native endian.
179 /// Asserts that the address is ip4 or ip6.
180 pub fn getPort(self: Address) u16 {
181 return switch (self.any.family) {
182 posix.AF.INET => self.in.getPort(),
183 posix.AF.INET6 => self.in6.getPort(),
184 else => unreachable,
185 };
186 }
187
188 /// `port` is native-endian.
189 /// Asserts that the address is ip4 or ip6.
190 pub fn setPort(self: *Address, port: u16) void {
191 switch (self.any.family) {
192 posix.AF.INET => self.in.setPort(port),
193 posix.AF.INET6 => self.in6.setPort(port),
194 else => unreachable,
195 }
196 }
197
198 /// Asserts that `addr` is an IP address.
199 /// This function will read past the end of the pointer, with a size depending
200 /// on the address family.
201 pub fn initPosix(addr: *align(4) const posix.sockaddr) Address {
202 switch (addr.family) {
203 posix.AF.INET => return Address{ .in = Ip4Address{ .sa = @as(*const posix.sockaddr.in, @ptrCast(addr)).* } },
204 posix.AF.INET6 => return Address{ .in6 = Ip6Address{ .sa = @as(*const posix.sockaddr.in6, @ptrCast(addr)).* } },
205 else => unreachable,
206 }
207 }
208
209 pub fn format(self: Address, w: *Io.Writer) Io.Writer.Error!void {
210 switch (self.any.family) {
211 posix.AF.INET => try self.in.format(w),
212 posix.AF.INET6 => try self.in6.format(w),
213 posix.AF.UNIX => {
214 if (!has_unix_sockets) unreachable;
215 try w.writeAll(std.mem.sliceTo(&self.un.path, 0));
216 },
217 else => unreachable,
218 }
219 }
220
221 pub fn eql(a: Address, b: Address) bool {
222 const a_bytes = @as([*]const u8, @ptrCast(&a.any))[0..a.getOsSockLen()];
223 const b_bytes = @as([*]const u8, @ptrCast(&b.any))[0..b.getOsSockLen()];
224 return mem.eql(u8, a_bytes, b_bytes);
225 }
226
227 pub fn getOsSockLen(self: Address) posix.socklen_t {
228 switch (self.any.family) {
229 posix.AF.INET => return self.in.getOsSockLen(),
230 posix.AF.INET6 => return self.in6.getOsSockLen(),
231 posix.AF.UNIX => {
232 if (!has_unix_sockets) {
233 unreachable;
234 }
235
236 // Using the full length of the structure here is more portable than returning
237 // the number of bytes actually used by the currently stored path.
238 // This also is correct regardless if we are passing a socket address to the kernel
239 // (e.g. in bind, connect, sendto) since we ensure the path is 0 terminated in
240 // initUnix() or if we are receiving a socket address from the kernel and must
241 // provide the full buffer size (e.g. getsockname, getpeername, recvfrom, accept).
242 //
243 // To access the path, std.mem.sliceTo(&address.un.path, 0) should be used.
244 return @as(posix.socklen_t, @intCast(@sizeOf(posix.sockaddr.un)));
245 },
246
247 else => unreachable,
248 }
249 }
250
251 pub const ListenError = posix.SocketError || posix.BindError || posix.ListenError ||
252 posix.SetSockOptError || posix.GetSockNameError;
253
254 pub const ListenOptions = struct {
255 /// How many connections the kernel will accept on the application's behalf.
256 /// If more than this many connections pool in the kernel, clients will start
257 /// seeing "Connection refused".
258 kernel_backlog: u31 = 128,
259 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
260 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
261 reuse_address: bool = false,
262 /// Sets O_NONBLOCK.
263 force_nonblocking: bool = false,
264 };
265
266 /// The returned `Server` has an open `stream`.
267 pub fn listen(address: Address, options: ListenOptions) ListenError!Server {
268 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
269 const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock;
270 const proto: u32 = if (address.any.family == posix.AF.UNIX) 0 else posix.IPPROTO.TCP;
271
272 const sockfd = try posix.socket(address.any.family, sock_flags, proto);
273 var s: Server = .{
274 .listen_address = undefined,
275 .stream = .{ .handle = sockfd },
276 };
277 errdefer s.stream.close();
278
279 if (options.reuse_address) {
280 try posix.setsockopt(
281 sockfd,
282 posix.SOL.SOCKET,
283 posix.SO.REUSEADDR,
284 &mem.toBytes(@as(c_int, 1)),
285 );
286 if (@hasDecl(posix.SO, "REUSEPORT") and address.any.family != posix.AF.UNIX) {
287 try posix.setsockopt(
288 sockfd,
289 posix.SOL.SOCKET,
290 posix.SO.REUSEPORT,
291 &mem.toBytes(@as(c_int, 1)),
292 );
293 }
294 }
295
296 var socklen = address.getOsSockLen();
297 try posix.bind(sockfd, &address.any, socklen);
298 try posix.listen(sockfd, options.kernel_backlog);
299 try posix.getsockname(sockfd, &s.listen_address.any, &socklen);
300 return s;
301 }
302};
303
304pub const Ip4Address = extern struct {
305 sa: posix.sockaddr.in,
306
307 pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address {
308 var result: Ip4Address = .{
309 .sa = .{
310 .port = mem.nativeToBig(u16, port),
311 .addr = undefined,
312 },
313 };
314 const out_ptr = mem.asBytes(&result.sa.addr);
315
316 var x: u8 = 0;
317 var index: u8 = 0;
318 var saw_any_digits = false;
319 var has_zero_prefix = false;
320 for (buf) |c| {
321 if (c == '.') {
322 if (!saw_any_digits) {
323 return error.InvalidCharacter;
324 }
325 if (index == 3) {
326 return error.InvalidEnd;
327 }
328 out_ptr[index] = x;
329 index += 1;
330 x = 0;
331 saw_any_digits = false;
332 has_zero_prefix = false;
333 } else if (c >= '0' and c <= '9') {
334 if (c == '0' and !saw_any_digits) {
335 has_zero_prefix = true;
336 } else if (has_zero_prefix) {
337 return error.NonCanonical;
338 }
339 saw_any_digits = true;
340 x = try std.math.mul(u8, x, 10);
341 x = try std.math.add(u8, x, c - '0');
342 } else {
343 return error.InvalidCharacter;
344 }
345 }
346 if (index == 3 and saw_any_digits) {
347 out_ptr[index] = x;
348 return result;
349 }
350
351 return error.Incomplete;
352 }
353
354 pub fn resolveIp(name: []const u8, port: u16) !Ip4Address {
355 if (parse(name, port)) |ip4| return ip4 else |err| switch (err) {
356 error.Overflow,
357 error.InvalidEnd,
358 error.InvalidCharacter,
359 error.Incomplete,
360 error.NonCanonical,
361 => {},
362 }
363 return error.InvalidIpAddressFormat;
364 }
365
366 pub fn init(addr: [4]u8, port: u16) Ip4Address {
367 return Ip4Address{
368 .sa = posix.sockaddr.in{
369 .port = mem.nativeToBig(u16, port),
370 .addr = @as(*align(1) const u32, @ptrCast(&addr)).*,
371 },
372 };
373 }
374
375 /// Returns the port in native endian.
376 /// Asserts that the address is ip4 or ip6.
377 pub fn getPort(self: Ip4Address) u16 {
378 return mem.bigToNative(u16, self.sa.port);
379 }
380
381 /// `port` is native-endian.
382 /// Asserts that the address is ip4 or ip6.
383 pub fn setPort(self: *Ip4Address, port: u16) void {
384 self.sa.port = mem.nativeToBig(u16, port);
385 }
386
387 pub fn format(self: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
388 const bytes: *const [4]u8 = @ptrCast(&self.sa.addr);
389 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], self.getPort() });
390 }
391
392 pub fn getOsSockLen(self: Ip4Address) posix.socklen_t {
393 _ = self;
394 return @sizeOf(posix.sockaddr.in);
395 }
396};
397
398pub const Ip6Address = extern struct {
399 sa: posix.sockaddr.in6,
400
401 /// Parse a given IPv6 address string into an Address.
402 /// Assumes the Scope ID of the address is fully numeric.
403 /// For non-numeric addresses, see `resolveIp6`.
404 pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address {
405 var result = Ip6Address{
406 .sa = posix.sockaddr.in6{
407 .scope_id = 0,
408 .port = mem.nativeToBig(u16, port),
409 .flowinfo = 0,
410 .addr = undefined,
411 },
412 };
413 var ip_slice: *[16]u8 = result.sa.addr[0..];
414
415 var tail: [16]u8 = undefined;
416
417 var x: u16 = 0;
418 var saw_any_digits = false;
419 var index: u8 = 0;
420 var scope_id = false;
421 var abbrv = false;
422 for (buf, 0..) |c, i| {
423 if (scope_id) {
424 if (c >= '0' and c <= '9') {
425 const digit = c - '0';
426 {
427 const ov = @mulWithOverflow(result.sa.scope_id, 10);
428 if (ov[1] != 0) return error.Overflow;
429 result.sa.scope_id = ov[0];
430 }
431 {
432 const ov = @addWithOverflow(result.sa.scope_id, digit);
433 if (ov[1] != 0) return error.Overflow;
434 result.sa.scope_id = ov[0];
435 }
436 } else {
437 return error.InvalidCharacter;
438 }
439 } else if (c == ':') {
440 if (!saw_any_digits) {
441 if (abbrv) return error.InvalidCharacter; // ':::'
442 if (i != 0) abbrv = true;
443 @memset(ip_slice[index..], 0);
444 ip_slice = tail[0..];
445 index = 0;
446 continue;
447 }
448 if (index == 14) {
449 return error.InvalidEnd;
450 }
451 ip_slice[index] = @as(u8, @truncate(x >> 8));
452 index += 1;
453 ip_slice[index] = @as(u8, @truncate(x));
454 index += 1;
455
456 x = 0;
457 saw_any_digits = false;
458 } else if (c == '%') {
459 if (!saw_any_digits) {
460 return error.InvalidCharacter;
461 }
462 scope_id = true;
463 saw_any_digits = false;
464 } else if (c == '.') {
465 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
466 // must start with '::ffff:'
467 return error.InvalidIpv4Mapping;
468 }
469 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
470 const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
471 return error.InvalidIpv4Mapping;
472 }).sa.addr;
473 ip_slice = result.sa.addr[0..];
474 ip_slice[10] = 0xff;
475 ip_slice[11] = 0xff;
476
477 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
478
479 ip_slice[12] = ptr[0];
480 ip_slice[13] = ptr[1];
481 ip_slice[14] = ptr[2];
482 ip_slice[15] = ptr[3];
483 return result;
484 } else {
485 const digit = try std.fmt.charToDigit(c, 16);
486 {
487 const ov = @mulWithOverflow(x, 16);
488 if (ov[1] != 0) return error.Overflow;
489 x = ov[0];
490 }
491 {
492 const ov = @addWithOverflow(x, digit);
493 if (ov[1] != 0) return error.Overflow;
494 x = ov[0];
495 }
496 saw_any_digits = true;
497 }
498 }
499
500 if (!saw_any_digits and !abbrv) {
501 return error.Incomplete;
502 }
503 if (!abbrv and index < 14) {
504 return error.Incomplete;
505 }
506
507 if (index == 14) {
508 ip_slice[14] = @as(u8, @truncate(x >> 8));
509 ip_slice[15] = @as(u8, @truncate(x));
510 return result;
511 } else {
512 ip_slice[index] = @as(u8, @truncate(x >> 8));
513 index += 1;
514 ip_slice[index] = @as(u8, @truncate(x));
515 index += 1;
516 @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
517 return result;
518 }
519 }
520
521 pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address {
522 // TODO: Unify the implementations of resolveIp6 and parseIp6.
523 var result = Ip6Address{
524 .sa = posix.sockaddr.in6{
525 .scope_id = 0,
526 .port = mem.nativeToBig(u16, port),
527 .flowinfo = 0,
528 .addr = undefined,
529 },
530 };
531 var ip_slice: *[16]u8 = result.sa.addr[0..];
532
533 var tail: [16]u8 = undefined;
534
535 var x: u16 = 0;
536 var saw_any_digits = false;
537 var index: u8 = 0;
538 var abbrv = false;
539
540 var scope_id = false;
541 var scope_id_value: [posix.IFNAMESIZE - 1]u8 = undefined;
542 var scope_id_index: usize = 0;
543
544 for (buf, 0..) |c, i| {
545 if (scope_id) {
546 // Handling of percent-encoding should be for an URI library.
547 if ((c >= '0' and c <= '9') or
548 (c >= 'A' and c <= 'Z') or
549 (c >= 'a' and c <= 'z') or
550 (c == '-') or (c == '.') or (c == '_') or (c == '~'))
551 {
552 if (scope_id_index >= scope_id_value.len) {
553 return error.Overflow;
554 }
555
556 scope_id_value[scope_id_index] = c;
557 scope_id_index += 1;
558 } else {
559 return error.InvalidCharacter;
560 }
561 } else if (c == ':') {
562 if (!saw_any_digits) {
563 if (abbrv) return error.InvalidCharacter; // ':::'
564 if (i != 0) abbrv = true;
565 @memset(ip_slice[index..], 0);
566 ip_slice = tail[0..];
567 index = 0;
568 continue;
569 }
570 if (index == 14) {
571 return error.InvalidEnd;
572 }
573 ip_slice[index] = @as(u8, @truncate(x >> 8));
574 index += 1;
575 ip_slice[index] = @as(u8, @truncate(x));
576 index += 1;
577
578 x = 0;
579 saw_any_digits = false;
580 } else if (c == '%') {
581 if (!saw_any_digits) {
582 return error.InvalidCharacter;
583 }
584 scope_id = true;
585 saw_any_digits = false;
586 } else if (c == '.') {
587 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
588 // must start with '::ffff:'
589 return error.InvalidIpv4Mapping;
590 }
591 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
592 const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
593 return error.InvalidIpv4Mapping;
594 }).sa.addr;
595 ip_slice = result.sa.addr[0..];
596 ip_slice[10] = 0xff;
597 ip_slice[11] = 0xff;
598
599 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
600
601 ip_slice[12] = ptr[0];
602 ip_slice[13] = ptr[1];
603 ip_slice[14] = ptr[2];
604 ip_slice[15] = ptr[3];
605 return result;
606 } else {
607 const digit = try std.fmt.charToDigit(c, 16);
608 {
609 const ov = @mulWithOverflow(x, 16);
610 if (ov[1] != 0) return error.Overflow;
611 x = ov[0];
612 }
613 {
614 const ov = @addWithOverflow(x, digit);
615 if (ov[1] != 0) return error.Overflow;
616 x = ov[0];
617 }
618 saw_any_digits = true;
619 }
620 }
621
622 if (!saw_any_digits and !abbrv) {
623 return error.Incomplete;
624 }
625
626 if (scope_id and scope_id_index == 0) {
627 return error.Incomplete;
628 }
629
630 var resolved_scope_id: u32 = 0;
631 if (scope_id_index > 0) {
632 const scope_id_str = scope_id_value[0..scope_id_index];
633 resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
634 if (err != error.InvalidCharacter) return err;
635 break :blk try if_nametoindex(scope_id_str);
636 };
637 }
638
639 result.sa.scope_id = resolved_scope_id;
640
641 if (index == 14) {
642 ip_slice[14] = @as(u8, @truncate(x >> 8));
643 ip_slice[15] = @as(u8, @truncate(x));
644 return result;
645 } else {
646 ip_slice[index] = @as(u8, @truncate(x >> 8));
647 index += 1;
648 ip_slice[index] = @as(u8, @truncate(x));
649 index += 1;
650 @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
651 return result;
652 }
653 }
654
655 pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address {
656 return Ip6Address{
657 .sa = posix.sockaddr.in6{
658 .addr = addr,
659 .port = mem.nativeToBig(u16, port),
660 .flowinfo = flowinfo,
661 .scope_id = scope_id,
662 },
663 };
664 }
665
666 /// Returns the port in native endian.
667 /// Asserts that the address is ip4 or ip6.
668 pub fn getPort(self: Ip6Address) u16 {
669 return mem.bigToNative(u16, self.sa.port);
670 }
671
672 /// `port` is native-endian.
673 /// Asserts that the address is ip4 or ip6.
674 pub fn setPort(self: *Ip6Address, port: u16) void {
675 self.sa.port = mem.nativeToBig(u16, port);
676 }
677
678 pub fn format(self: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
679 const port = mem.bigToNative(u16, self.sa.port);
680 if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
681 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{
682 self.sa.addr[12],
683 self.sa.addr[13],
684 self.sa.addr[14],
685 self.sa.addr[15],
686 port,
687 });
688 return;
689 }
690 const big_endian_parts = @as(*align(1) const [8]u16, @ptrCast(&self.sa.addr));
691 const native_endian_parts = switch (native_endian) {
692 .big => big_endian_parts.*,
693 .little => blk: {
694 var buf: [8]u16 = undefined;
695 for (big_endian_parts, 0..) |part, i| {
696 buf[i] = mem.bigToNative(u16, part);
697 }
698 break :blk buf;
699 },
700 };
701
702 // Find the longest zero run
703 var longest_start: usize = 8;
704 var longest_len: usize = 0;
705 var current_start: usize = 0;
706 var current_len: usize = 0;
707
708 for (native_endian_parts, 0..) |part, i| {
709 if (part == 0) {
710 if (current_len == 0) {
711 current_start = i;
712 }
713 current_len += 1;
714 if (current_len > longest_len) {
715 longest_start = current_start;
716 longest_len = current_len;
717 }
718 } else {
719 current_len = 0;
720 }
721 }
722
723 // Only compress if the longest zero run is 2 or more
724 if (longest_len < 2) {
725 longest_start = 8;
726 longest_len = 0;
727 }
728
729 try w.writeAll("[");
730 var i: usize = 0;
731 var abbrv = false;
732 while (i < native_endian_parts.len) : (i += 1) {
733 if (i == longest_start) {
734 // Emit "::" for the longest zero run
735 if (!abbrv) {
736 try w.writeAll(if (i == 0) "::" else ":");
737 abbrv = true;
738 }
739 i += longest_len - 1; // Skip the compressed range
740 continue;
741 }
742 if (abbrv) {
743 abbrv = false;
744 }
745 try w.print("{x}", .{native_endian_parts[i]});
746 if (i != native_endian_parts.len - 1) {
747 try w.writeAll(":");
748 }
749 }
750 if (self.sa.scope_id != 0) {
751 try w.print("%{}", .{self.sa.scope_id});
752 }
753 try w.print("]:{}", .{port});
754 }
755
756 pub fn getOsSockLen(self: Ip6Address) posix.socklen_t {
757 _ = self;
758 return @sizeOf(posix.sockaddr.in6);
759 }
760};
761
762pub fn connectUnixSocket(path: []const u8) !Stream {
763 const opt_non_block = 0;
764 const sockfd = try posix.socket(
765 posix.AF.UNIX,
766 posix.SOCK.STREAM | posix.SOCK.CLOEXEC | opt_non_block,
767 0,
768 );
769 errdefer Stream.close(.{ .handle = sockfd });
770
771 var addr = try Address.initUnix(path);
772 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
773
774 return .{ .handle = sockfd };
775}
776
777fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
778 if (native_os == .linux) {
779 var ifr: posix.ifreq = undefined;
780 const sockfd = try posix.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0);
781 defer Stream.close(.{ .handle = sockfd });
782
783 @memcpy(ifr.ifrn.name[0..name.len], name);
784 ifr.ifrn.name[name.len] = 0;
785
786 // TODO investigate if this needs to be integrated with evented I/O.
787 try posix.ioctl_SIOCGIFINDEX(sockfd, &ifr);
788
789 return @bitCast(ifr.ifru.ivalue);
790 }
791
792 if (native_os.isDarwin()) {
793 if (name.len >= posix.IFNAMESIZE)
794 return error.NameTooLong;
795
796 var if_name: [posix.IFNAMESIZE:0]u8 = undefined;
797 @memcpy(if_name[0..name.len], name);
798 if_name[name.len] = 0;
799 const if_slice = if_name[0..name.len :0];
800 const index = std.c.if_nametoindex(if_slice);
801 if (index == 0)
802 return error.InterfaceNotFound;
803 return @as(u32, @bitCast(index));
804 }
805
806 if (native_os == .windows) {
807 if (name.len >= posix.IFNAMESIZE)
808 return error.NameTooLong;
809
810 var interface_name: [posix.IFNAMESIZE:0]u8 = undefined;
811 @memcpy(interface_name[0..name.len], name);
812 interface_name[name.len] = 0;
813 const index = std.os.windows.ws2_32.if_nametoindex(@as([*:0]const u8, &interface_name));
814 if (index == 0)
815 return error.InterfaceNotFound;
816 return index;
817 }
818
819 @compileError("std.net.if_nametoindex unimplemented for this OS");
820}
821
822pub const AddressList = struct {
823 arena: std.heap.ArenaAllocator,
824 addrs: []Address,
825 canon_name: ?[]u8,
826
827 pub fn deinit(self: *AddressList) void {
828 // Here we copy the arena allocator into stack memory, because
829 // otherwise it would destroy itself while it was still working.
830 var arena = self.arena;
831 arena.deinit();
832 // self is destroyed
833 }
834};
835
836pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
837
838/// All memory allocated with `allocator` will be freed before this function returns.
839pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
840 const list = try getAddressList(allocator, name, port);
841 defer list.deinit();
842
843 if (list.addrs.len == 0) return error.UnknownHostName;
844
845 for (list.addrs) |addr| {
846 return tcpConnectToAddress(addr) catch |err| switch (err) {
847 error.ConnectionRefused => {
848 continue;
849 },
850 else => return err,
851 };
852 }
853 return posix.ConnectError.ConnectionRefused;
854}
855
856pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError;
857
858pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
859 const nonblock = 0;
860 const sock_flags = posix.SOCK.STREAM | nonblock |
861 (if (native_os == .windows) 0 else posix.SOCK.CLOEXEC);
862 const sockfd = try posix.socket(address.any.family, sock_flags, posix.IPPROTO.TCP);
863 errdefer Stream.close(.{ .handle = sockfd });
864
865 try posix.connect(sockfd, &address.any, address.getOsSockLen());
866
867 return Stream{ .handle = sockfd };
868}
869
870// TODO: Instead of having a massive error set, make the error set have categories, and then
871// store the sub-error as a diagnostic value.
872const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
873 TemporaryNameServerFailure,
874 NameServerFailure,
875 AddressFamilyNotSupported,
876 UnknownHostName,
877 ServiceUnavailable,
878 Unexpected,
879
880 HostLacksNetworkAddresses,
881
882 InvalidCharacter,
883 InvalidEnd,
884 NonCanonical,
885 Overflow,
886 Incomplete,
887 InvalidIpv4Mapping,
888 InvalidIpAddressFormat,
889
890 InterfaceNotFound,
891 FileSystem,
892 ResolveConfParseFailed,
893};
894
895/// Call `AddressList.deinit` on the result.
896pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
897 const result = blk: {
898 var arena = std.heap.ArenaAllocator.init(gpa);
899 errdefer arena.deinit();
900
901 const result = try arena.allocator().create(AddressList);
902 result.* = AddressList{
903 .arena = arena,
904 .addrs = undefined,
905 .canon_name = null,
906 };
907 break :blk result;
908 };
909 const arena = result.arena.allocator();
910 errdefer result.deinit();
911
912 if (native_os == .windows) {
913 const name_c = try gpa.dupeZ(u8, name);
914 defer gpa.free(name_c);
915
916 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
917 defer gpa.free(port_c);
918
919 const ws2_32 = windows.ws2_32;
920 const hints: posix.addrinfo = .{
921 .flags = .{ .NUMERICSERV = true },
922 .family = posix.AF.UNSPEC,
923 .socktype = posix.SOCK.STREAM,
924 .protocol = posix.IPPROTO.TCP,
925 .canonname = null,
926 .addr = null,
927 .addrlen = 0,
928 .next = null,
929 };
930 var res: ?*posix.addrinfo = null;
931 var first = true;
932 while (true) {
933 const rc = ws2_32.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res);
934 switch (@as(windows.ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(rc))))) {
935 @as(windows.ws2_32.WinsockError, @enumFromInt(0)) => break,
936 .WSATRY_AGAIN => return error.TemporaryNameServerFailure,
937 .WSANO_RECOVERY => return error.NameServerFailure,
938 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
939 .WSA_NOT_ENOUGH_MEMORY => return error.OutOfMemory,
940 .WSAHOST_NOT_FOUND => return error.UnknownHostName,
941 .WSATYPE_NOT_FOUND => return error.ServiceUnavailable,
942 .WSAEINVAL => unreachable,
943 .WSAESOCKTNOSUPPORT => unreachable,
944 .WSANOTINITIALISED => {
945 if (!first) return error.Unexpected;
946 first = false;
947 try windows.callWSAStartup();
948 continue;
949 },
950 else => |err| return windows.unexpectedWSAError(err),
951 }
952 }
953 defer ws2_32.freeaddrinfo(res);
954
955 const addr_count = blk: {
956 var count: usize = 0;
957 var it = res;
958 while (it) |info| : (it = info.next) {
959 if (info.addr != null) {
960 count += 1;
961 }
962 }
963 break :blk count;
964 };
965 result.addrs = try arena.alloc(Address, addr_count);
966
967 var it = res;
968 var i: usize = 0;
969 while (it) |info| : (it = info.next) {
970 const addr = info.addr orelse continue;
971 result.addrs[i] = Address.initPosix(@alignCast(addr));
972
973 if (info.canonname) |n| {
974 if (result.canon_name == null) {
975 result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0));
976 }
977 }
978 i += 1;
979 }
980
981 return result;
982 }
983
984 if (builtin.link_libc) {
985 const name_c = try gpa.dupeZ(u8, name);
986 defer gpa.free(name_c);
987
988 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
989 defer gpa.free(port_c);
990
991 const hints: posix.addrinfo = .{
992 .flags = .{ .NUMERICSERV = true },
993 .family = posix.AF.UNSPEC,
994 .socktype = posix.SOCK.STREAM,
995 .protocol = posix.IPPROTO.TCP,
996 .canonname = null,
997 .addr = null,
998 .addrlen = 0,
999 .next = null,
1000 };
1001 var res: ?*posix.addrinfo = null;
1002 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
1003 @as(posix.system.EAI, @enumFromInt(0)) => {},
1004 .ADDRFAMILY => return error.HostLacksNetworkAddresses,
1005 .AGAIN => return error.TemporaryNameServerFailure,
1006 .BADFLAGS => unreachable, // Invalid hints
1007 .FAIL => return error.NameServerFailure,
1008 .FAMILY => return error.AddressFamilyNotSupported,
1009 .MEMORY => return error.OutOfMemory,
1010 .NODATA => return error.HostLacksNetworkAddresses,
1011 .NONAME => return error.UnknownHostName,
1012 .SERVICE => return error.ServiceUnavailable,
1013 .SOCKTYPE => unreachable, // Invalid socket type requested in hints
1014 .SYSTEM => switch (posix.errno(-1)) {
1015 else => |e| return posix.unexpectedErrno(e),
1016 },
1017 else => unreachable,
1018 }
1019 defer if (res) |some| posix.system.freeaddrinfo(some);
1020
1021 const addr_count = blk: {
1022 var count: usize = 0;
1023 var it = res;
1024 while (it) |info| : (it = info.next) {
1025 if (info.addr != null) {
1026 count += 1;
1027 }
1028 }
1029 break :blk count;
1030 };
1031 result.addrs = try arena.alloc(Address, addr_count);
1032
1033 var it = res;
1034 var i: usize = 0;
1035 while (it) |info| : (it = info.next) {
1036 const addr = info.addr orelse continue;
1037 result.addrs[i] = Address.initPosix(@alignCast(addr));
1038
1039 if (info.canonname) |n| {
1040 if (result.canon_name == null) {
1041 result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0));
1042 }
1043 }
1044 i += 1;
1045 }
1046
1047 return result;
1048 }
1049
1050 if (native_os == .linux) {
1051 const family = posix.AF.UNSPEC;
1052 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1053 defer lookup_addrs.deinit(gpa);
1054
1055 var canon: ArrayList(u8) = .empty;
1056 defer canon.deinit(gpa);
1057
1058 try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);
1059
1060 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
1061 if (canon.items.len != 0) {
1062 result.canon_name = try arena.dupe(u8, canon.items);
1063 }
1064
1065 for (lookup_addrs.items, 0..) |lookup_addr, i| {
1066 result.addrs[i] = lookup_addr.addr;
1067 assert(result.addrs[i].getPort() == port);
1068 }
1069
1070 return result;
1071 }
1072 @compileError("std.net.getAddressList unimplemented for this OS");
1073}
1074
1075const LookupAddr = struct {
1076 addr: Address,
1077 sortkey: i32 = 0,
1078};
1079
1080const DAS_USABLE = 0x40000000;
1081const DAS_MATCHINGSCOPE = 0x20000000;
1082const DAS_MATCHINGLABEL = 0x10000000;
1083const DAS_PREC_SHIFT = 20;
1084const DAS_SCOPE_SHIFT = 16;
1085const DAS_PREFIX_SHIFT = 8;
1086const DAS_ORDER_SHIFT = 0;
1087
1088fn linuxLookupName(
1089 gpa: Allocator,
1090 addrs: *ArrayList(LookupAddr),
1091 canon: *ArrayList(u8),
1092 opt_name: ?[]const u8,
1093 family: posix.sa_family_t,
1094 flags: posix.AI,
1095 port: u16,
1096) !void {
1097 if (opt_name) |name| {
1098 // reject empty name and check len so it fits into temp bufs
1099 canon.items.len = 0;
1100 try canon.appendSlice(gpa, name);
1101 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1102 try addrs.append(gpa, .{ .addr = addr });
1103 } else |name_err| if (flags.NUMERICHOST) {
1104 return name_err;
1105 } else {
1106 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
1107 if (addrs.items.len == 0) {
1108 // RFC 6761 Section 6.3.3
1109 // Name resolution APIs and libraries SHOULD recognize localhost
1110 // names as special and SHOULD always return the IP loopback address
1111 // for address queries and negative responses for all other query
1112 // types.
1113
1114 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
1115 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
1116 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {
1117 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1118 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1119 return;
1120 }
1121
1122 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
1123 }
1124 }
1125 } else {
1126 try canon.resize(gpa, 0);
1127 try addrs.ensureUnusedCapacity(gpa, 2);
1128 linuxLookupNameFromNull(addrs, family, flags, port);
1129 }
1130 if (addrs.items.len == 0) return error.UnknownHostName;
1131
1132 // No further processing is needed if there are fewer than 2
1133 // results or if there are only IPv4 results.
1134 if (addrs.items.len == 1 or family == posix.AF.INET) return;
1135 const all_ip4 = for (addrs.items) |addr| {
1136 if (addr.addr.any.family != posix.AF.INET) break false;
1137 } else true;
1138 if (all_ip4) return;
1139
1140 // The following implements a subset of RFC 3484/6724 destination
1141 // address selection by generating a single 31-bit sort key for
1142 // each address. Rules 3, 4, and 7 are omitted for having
1143 // excessive runtime and code size cost and dubious benefit.
1144 // So far the label/precedence table cannot be customized.
1145 // This implementation is ported from musl libc.
1146 // A more idiomatic "ziggy" implementation would be welcome.
1147 for (addrs.items, 0..) |*addr, i| {
1148 var key: i32 = 0;
1149 var sa6: posix.sockaddr.in6 = undefined;
1150 @memset(@as([*]u8, @ptrCast(&sa6))[0..@sizeOf(posix.sockaddr.in6)], 0);
1151 var da6 = posix.sockaddr.in6{
1152 .family = posix.AF.INET6,
1153 .scope_id = addr.addr.in6.sa.scope_id,
1154 .port = 65535,
1155 .flowinfo = 0,
1156 .addr = [1]u8{0} ** 16,
1157 };
1158 var sa4: posix.sockaddr.in = undefined;
1159 @memset(@as([*]u8, @ptrCast(&sa4))[0..@sizeOf(posix.sockaddr.in)], 0);
1160 var da4 = posix.sockaddr.in{
1161 .family = posix.AF.INET,
1162 .port = 65535,
1163 .addr = 0,
1164 .zero = [1]u8{0} ** 8,
1165 };
1166 var sa: *align(4) posix.sockaddr = undefined;
1167 var da: *align(4) posix.sockaddr = undefined;
1168 var salen: posix.socklen_t = undefined;
1169 var dalen: posix.socklen_t = undefined;
1170 if (addr.addr.any.family == posix.AF.INET6) {
1171 da6.addr = addr.addr.in6.sa.addr;
1172 da = @ptrCast(&da6);
1173 dalen = @sizeOf(posix.sockaddr.in6);
1174 sa = @ptrCast(&sa6);
1175 salen = @sizeOf(posix.sockaddr.in6);
1176 } else {
1177 sa6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1178 da6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1179 mem.writeInt(u32, da6.addr[12..], addr.addr.in.sa.addr, native_endian);
1180 da4.addr = addr.addr.in.sa.addr;
1181 da = @ptrCast(&da4);
1182 dalen = @sizeOf(posix.sockaddr.in);
1183 sa = @ptrCast(&sa4);
1184 salen = @sizeOf(posix.sockaddr.in);
1185 }
1186 const dpolicy = policyOf(da6.addr);
1187 const dscope: i32 = scopeOf(da6.addr);
1188 const dlabel = dpolicy.label;
1189 const dprec: i32 = dpolicy.prec;
1190 const MAXADDRS = 3;
1191 var prefixlen: i32 = 0;
1192 const sock_flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC;
1193 if (posix.socket(addr.addr.any.family, sock_flags, posix.IPPROTO.UDP)) |fd| syscalls: {
1194 defer Stream.close(.{ .handle = fd });
1195 posix.connect(fd, da, dalen) catch break :syscalls;
1196 key |= DAS_USABLE;
1197 posix.getsockname(fd, sa, &salen) catch break :syscalls;
1198 if (addr.addr.any.family == posix.AF.INET) {
1199 mem.writeInt(u32, sa6.addr[12..16], sa4.addr, native_endian);
1200 }
1201 if (dscope == @as(i32, scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE;
1202 if (dlabel == labelOf(sa6.addr)) key |= DAS_MATCHINGLABEL;
1203 prefixlen = prefixMatch(sa6.addr, da6.addr);
1204 } else |_| {}
1205 key |= dprec << DAS_PREC_SHIFT;
1206 key |= (15 - dscope) << DAS_SCOPE_SHIFT;
1207 key |= prefixlen << DAS_PREFIX_SHIFT;
1208 key |= (MAXADDRS - @as(i32, @intCast(i))) << DAS_ORDER_SHIFT;
1209 addr.sortkey = key;
1210 }
1211 mem.sort(LookupAddr, addrs.items, {}, addrCmpLessThan);
1212}
1213
1214const Policy = struct {
1215 addr: [16]u8,
1216 len: u8,
1217 mask: u8,
1218 prec: u8,
1219 label: u8,
1220};
1221
1222const defined_policies = [_]Policy{
1223 Policy{
1224 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
1225 .len = 15,
1226 .mask = 0xff,
1227 .prec = 50,
1228 .label = 0,
1229 },
1230 Policy{
1231 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
1232 .len = 11,
1233 .mask = 0xff,
1234 .prec = 35,
1235 .label = 4,
1236 },
1237 Policy{
1238 .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1239 .len = 1,
1240 .mask = 0xff,
1241 .prec = 30,
1242 .label = 2,
1243 },
1244 Policy{
1245 .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1246 .len = 3,
1247 .mask = 0xff,
1248 .prec = 5,
1249 .label = 5,
1250 },
1251 Policy{
1252 .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1253 .len = 0,
1254 .mask = 0xfe,
1255 .prec = 3,
1256 .label = 13,
1257 },
1258 // These are deprecated and/or returned to the address
1259 // pool, so despite the RFC, treating them as special
1260 // is probably wrong.
1261 // { "", 11, 0xff, 1, 3 },
1262 // { "\xfe\xc0", 1, 0xc0, 1, 11 },
1263 // { "\x3f\xfe", 1, 0xff, 1, 12 },
1264 // Last rule must match all addresses to stop loop.
1265 Policy{
1266 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1267 .len = 0,
1268 .mask = 0,
1269 .prec = 40,
1270 .label = 1,
1271 },
1272};
1273
1274fn policyOf(a: [16]u8) *const Policy {
1275 for (&defined_policies) |*policy| {
1276 if (!mem.eql(u8, a[0..policy.len], policy.addr[0..policy.len])) continue;
1277 if ((a[policy.len] & policy.mask) != policy.addr[policy.len]) continue;
1278 return policy;
1279 }
1280 unreachable;
1281}
1282
1283fn scopeOf(a: [16]u8) u8 {
1284 if (IN6_IS_ADDR_MULTICAST(a)) return a[1] & 15;
1285 if (IN6_IS_ADDR_LINKLOCAL(a)) return 2;
1286 if (IN6_IS_ADDR_LOOPBACK(a)) return 2;
1287 if (IN6_IS_ADDR_SITELOCAL(a)) return 5;
1288 return 14;
1289}
1290
1291fn prefixMatch(s: [16]u8, d: [16]u8) u8 {
1292 // TODO: This FIXME inherited from porting from musl libc.
1293 // I don't want this to go into zig std lib 1.0.0.
1294
1295 // FIXME: The common prefix length should be limited to no greater
1296 // than the nominal length of the prefix portion of the source
1297 // address. However the definition of the source prefix length is
1298 // not clear and thus this limiting is not yet implemented.
1299 var i: u8 = 0;
1300 while (i < 128 and ((s[i / 8] ^ d[i / 8]) & (@as(u8, 128) >> @as(u3, @intCast(i % 8)))) == 0) : (i += 1) {}
1301 return i;
1302}
1303
1304fn labelOf(a: [16]u8) u8 {
1305 return policyOf(a).label;
1306}
1307
1308fn IN6_IS_ADDR_MULTICAST(a: [16]u8) bool {
1309 return a[0] == 0xff;
1310}
1311
1312fn IN6_IS_ADDR_LINKLOCAL(a: [16]u8) bool {
1313 return a[0] == 0xfe and (a[1] & 0xc0) == 0x80;
1314}
1315
1316fn IN6_IS_ADDR_LOOPBACK(a: [16]u8) bool {
1317 return a[0] == 0 and a[1] == 0 and
1318 a[2] == 0 and
1319 a[12] == 0 and a[13] == 0 and
1320 a[14] == 0 and a[15] == 1;
1321}
1322
1323fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool {
1324 return a[0] == 0xfe and (a[1] & 0xc0) == 0xc0;
1325}
1326
1327// Parameters `b` and `a` swapped to make this descending.
1328fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
1329 _ = context;
1330 return a.sortkey < b.sortkey;
1331}
1332
1333fn linuxLookupNameFromNull(
1334 addrs: *ArrayList(LookupAddr),
1335 family: posix.sa_family_t,
1336 flags: posix.AI,
1337 port: u16,
1338) void {
1339 if (flags.PASSIVE) {
1340 if (family != posix.AF.INET6) {
1341 addrs.appendAssumeCapacity(.{
1342 .addr = Address.initIp4([1]u8{0} ** 4, port),
1343 });
1344 }
1345 if (family != posix.AF.INET) {
1346 addrs.appendAssumeCapacity(.{
1347 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1348 });
1349 }
1350 } else {
1351 if (family != posix.AF.INET6) {
1352 addrs.appendAssumeCapacity(.{
1353 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1354 });
1355 }
1356 if (family != posix.AF.INET) {
1357 addrs.appendAssumeCapacity(.{
1358 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1359 });
1360 }
1361 }
1362}
1363
1364fn linuxLookupNameFromHosts(
1365 gpa: Allocator,
1366 addrs: *ArrayList(LookupAddr),
1367 canon: *ArrayList(u8),
1368 name: []const u8,
1369 family: posix.sa_family_t,
1370 port: u16,
1371) !void {
1372 const file = fs.openFileAbsoluteZ("/etc/hosts", .{}) catch |err| switch (err) {
1373 error.FileNotFound,
1374 error.NotDir,
1375 error.AccessDenied,
1376 => return,
1377 else => |e| return e,
1378 };
1379 defer file.close();
1380
1381 var line_buf: [512]u8 = undefined;
1382 var file_reader = file.reader(&line_buf);
1383 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
1384 error.OutOfMemory => return error.OutOfMemory,
1385 error.ReadFailed => return file_reader.err.?,
1386 };
1387}
1388
1389fn parseHosts(
1390 gpa: Allocator,
1391 addrs: *ArrayList(LookupAddr),
1392 canon: *ArrayList(u8),
1393 name: []const u8,
1394 family: posix.sa_family_t,
1395 port: u16,
1396 br: *Io.Reader,
1397) error{ OutOfMemory, ReadFailed }!void {
1398 while (true) {
1399 const line = br.takeDelimiter('\n') catch |err| switch (err) {
1400 error.StreamTooLong => {
1401 // Skip lines that are too long.
1402 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {
1403 error.EndOfStream => break,
1404 error.ReadFailed => return error.ReadFailed,
1405 };
1406 continue;
1407 },
1408 error.ReadFailed => return error.ReadFailed,
1409 } orelse {
1410 break; // end of stream
1411 };
1412 var split_it = mem.splitScalar(u8, line, '#');
1413 const no_comment_line = split_it.first();
1414
1415 var line_it = mem.tokenizeAny(u8, no_comment_line, " \t");
1416 const ip_text = line_it.next() orelse continue;
1417 var first_name_text: ?[]const u8 = null;
1418 while (line_it.next()) |name_text| {
1419 if (first_name_text == null) first_name_text = name_text;
1420 if (mem.eql(u8, name_text, name)) {
1421 break;
1422 }
1423 } else continue;
1424
1425 const addr = Address.parseExpectingFamily(ip_text, family, port) catch |err| switch (err) {
1426 error.Overflow,
1427 error.InvalidEnd,
1428 error.InvalidCharacter,
1429 error.Incomplete,
1430 error.InvalidIpAddressFormat,
1431 error.InvalidIpv4Mapping,
1432 error.NonCanonical,
1433 => continue,
1434 };
1435 try addrs.append(gpa, .{ .addr = addr });
1436
1437 // first name is canonical name
1438 const name_text = first_name_text.?;
1439 if (isValidHostName(name_text)) {
1440 canon.items.len = 0;
1441 try canon.appendSlice(gpa, name_text);
1442 }
1443 }
1444}
1445
1446test parseHosts {
1447 if (builtin.os.tag == .wasi) {
1448 // TODO parsing addresses should not have OS dependencies
1449 return error.SkipZigTest;
1450 }
1451 var reader: Io.Reader = .fixed(
1452 \\127.0.0.1 localhost
1453 \\::1 localhost
1454 \\127.0.0.2 abcd
1455 );
1456 var addrs: ArrayList(LookupAddr) = .empty;
1457 defer addrs.deinit(std.testing.allocator);
1458 var canon: ArrayList(u8) = .empty;
1459 defer canon.deinit(std.testing.allocator);
1460 try parseHosts(std.testing.allocator, &addrs, &canon, "abcd", posix.AF.UNSPEC, 1234, &reader);
1461 try std.testing.expectEqual(1, addrs.items.len);
1462 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
1463}
1464
1465pub fn isValidHostName(bytes: []const u8) bool {
1466 _ = std.Io.net.HostName.init(bytes) catch return false;
1467 return true;
1468}
1469
1470fn linuxLookupNameFromDnsSearch(
1471 gpa: Allocator,
1472 addrs: *ArrayList(LookupAddr),
1473 canon: *ArrayList(u8),
1474 name: []const u8,
1475 family: posix.sa_family_t,
1476 port: u16,
1477) !void {
1478 var rc: ResolvConf = undefined;
1479 rc.init(gpa) catch return error.ResolveConfParseFailed;
1480 defer rc.deinit();
1481
1482 // Count dots, suppress search when >=ndots or name ends in
1483 // a dot, which is an explicit request for global scope.
1484 var dots: usize = 0;
1485 for (name) |byte| {
1486 if (byte == '.') dots += 1;
1487 }
1488
1489 const search = if (dots >= rc.ndots or mem.endsWith(u8, name, "."))
1490 ""
1491 else
1492 rc.search.items;
1493
1494 var canon_name = name;
1495
1496 // Strip final dot for canon, fail if multiple trailing dots.
1497 if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
1498 if (mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
1499
1500 // Name with search domain appended is setup in canon[]. This both
1501 // provides the desired default canonical name (if the requested
1502 // name is not a CNAME record) and serves as a buffer for passing
1503 // the full requested name to name_from_dns.
1504 try canon.resize(gpa, canon_name.len);
1505 @memcpy(canon.items, canon_name);
1506 try canon.append(gpa, '.');
1507
1508 var tok_it = mem.tokenizeAny(u8, search, " \t");
1509 while (tok_it.next()) |tok| {
1510 canon.shrinkRetainingCapacity(canon_name.len + 1);
1511 try canon.appendSlice(gpa, tok);
1512 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
1513 if (addrs.items.len != 0) return;
1514 }
1515
1516 canon.shrinkRetainingCapacity(canon_name.len);
1517 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
1518}
1519
1520const dpc_ctx = struct {
1521 gpa: Allocator,
1522 addrs: *ArrayList(LookupAddr),
1523 canon: *ArrayList(u8),
1524 port: u16,
1525};
1526
1527fn linuxLookupNameFromDns(
1528 gpa: Allocator,
1529 addrs: *ArrayList(LookupAddr),
1530 canon: *ArrayList(u8),
1531 name: []const u8,
1532 family: posix.sa_family_t,
1533 rc: ResolvConf,
1534 port: u16,
1535) !void {
1536 const ctx: dpc_ctx = .{
1537 .gpa = gpa,
1538 .addrs = addrs,
1539 .canon = canon,
1540 .port = port,
1541 };
1542 const AfRr = struct {
1543 af: posix.sa_family_t,
1544 rr: u8,
1545 };
1546 const afrrs = [_]AfRr{
1547 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1548 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1549 };
1550 var qbuf: [2][280]u8 = undefined;
1551 var abuf: [2][512]u8 = undefined;
1552 var qp: [2][]const u8 = undefined;
1553 const apbuf = [2][]u8{ &abuf[0], &abuf[1] };
1554 var nq: usize = 0;
1555
1556 for (afrrs) |afrr| {
1557 if (family != afrr.af) {
1558 const len = posix.res_mkquery(0, name, 1, afrr.rr, &[_]u8{}, null, &qbuf[nq]);
1559 qp[nq] = qbuf[nq][0..len];
1560 nq += 1;
1561 }
1562 }
1563
1564 var ap = [2][]u8{ apbuf[0], apbuf[1] };
1565 ap[0].len = 0;
1566 ap[1].len = 0;
1567
1568 try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]);
1569
1570 var i: usize = 0;
1571 while (i < nq) : (i += 1) {
1572 dnsParse(ap[i], ctx, dnsParseCallback) catch {};
1573 }
1574
1575 if (addrs.items.len != 0) return;
1576 if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure;
1577 if ((ap[0][3] & 15) == 0) return error.UnknownHostName;
1578 if ((ap[0][3] & 15) == 3) return;
1579 return error.NameServerFailure;
1580}
1581
1582const ResolvConf = struct {
1583 gpa: Allocator,
1584 attempts: u32,
1585 ndots: u32,
1586 timeout: u32,
1587 search: ArrayList(u8),
1588 /// TODO there are actually only allowed to be maximum 3 nameservers, no need
1589 /// for an array list.
1590 ns: ArrayList(LookupAddr),
1591
1592 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1593 /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1594 fn init(rc: *ResolvConf, gpa: Allocator) !void {
1595 rc.* = .{
1596 .gpa = gpa,
1597 .ns = .empty,
1598 .search = .empty,
1599 .ndots = 1,
1600 .timeout = 5,
1601 .attempts = 2,
1602 };
1603 errdefer rc.deinit();
1604
1605 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1606 error.FileNotFound,
1607 error.NotDir,
1608 error.AccessDenied,
1609 => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53),
1610 else => |e| return e,
1611 };
1612 defer file.close();
1613
1614 var line_buf: [512]u8 = undefined;
1615 var file_reader = file.reader(&line_buf);
1616 return parse(rc, &file_reader.interface) catch |err| switch (err) {
1617 error.ReadFailed => return file_reader.err.?,
1618 else => |e| return e,
1619 };
1620 }
1621
1622 const Directive = enum { options, nameserver, domain, search };
1623 const Option = enum { ndots, attempts, timeout };
1624
1625 fn parse(rc: *ResolvConf, reader: *Io.Reader) !void {
1626 const gpa = rc.gpa;
1627 while (reader.takeSentinel('\n')) |line_with_comment| {
1628 const line = line: {
1629 var split = mem.splitScalar(u8, line_with_comment, '#');
1630 break :line split.first();
1631 };
1632 var line_it = mem.tokenizeAny(u8, line, " \t");
1633
1634 const token = line_it.next() orelse continue;
1635 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
1636 .options => while (line_it.next()) |sub_tok| {
1637 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1638 const name = colon_it.first();
1639 const value_txt = colon_it.next() orelse continue;
1640 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1641 error.Overflow => 255,
1642 error.InvalidCharacter => continue,
1643 };
1644 switch (std.meta.stringToEnum(Option, name) orelse continue) {
1645 .ndots => rc.ndots = @min(value, 15),
1646 .attempts => rc.attempts = @min(value, 10),
1647 .timeout => rc.timeout = @min(value, 60),
1648 }
1649 },
1650 .nameserver => {
1651 const ip_txt = line_it.next() orelse continue;
1652 try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53);
1653 },
1654 .domain, .search => {
1655 rc.search.items.len = 0;
1656 try rc.search.appendSlice(gpa, line_it.rest());
1657 },
1658 }
1659 } else |err| switch (err) {
1660 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
1661 else => |e| return e,
1662 }
1663
1664 if (rc.ns.items.len == 0) {
1665 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1666 }
1667 }
1668
1669 fn resMSendRc(
1670 rc: ResolvConf,
1671 queries: []const []const u8,
1672 answers: [][]u8,
1673 answer_bufs: []const []u8,
1674 ) !void {
1675 const gpa = rc.gpa;
1676 const timeout = 1000 * rc.timeout;
1677 const attempts = rc.attempts;
1678
1679 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1680 var family: posix.sa_family_t = posix.AF.INET;
1681
1682 var ns_list: ArrayList(Address) = .empty;
1683 defer ns_list.deinit(gpa);
1684
1685 try ns_list.resize(gpa, rc.ns.items.len);
1686
1687 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1688 ns.* = iplit.addr;
1689 assert(ns.getPort() == 53);
1690 if (iplit.addr.any.family != posix.AF.INET) {
1691 family = posix.AF.INET6;
1692 }
1693 }
1694
1695 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1696 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1697 error.AddressFamilyNotSupported => blk: {
1698 // Handle case where system lacks IPv6 support
1699 if (family == posix.AF.INET6) {
1700 family = posix.AF.INET;
1701 break :blk try posix.socket(posix.AF.INET, flags, 0);
1702 }
1703 return err;
1704 },
1705 else => |e| return e,
1706 };
1707 defer Stream.close(.{ .handle = fd });
1708
1709 // Past this point, there are no errors. Each individual query will
1710 // yield either no reply (indicated by zero length) or an answer
1711 // packet which is up to the caller to interpret.
1712
1713 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1714 if (family == posix.AF.INET6) {
1715 try posix.setsockopt(
1716 fd,
1717 posix.SOL.IPV6,
1718 std.os.linux.IPV6.V6ONLY,
1719 &mem.toBytes(@as(c_int, 0)),
1720 );
1721 for (ns_list.items) |*ns| {
1722 if (ns.any.family != posix.AF.INET) continue;
1723 mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian);
1724 ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1725 ns.any.family = posix.AF.INET6;
1726 ns.in6.sa.flowinfo = 0;
1727 ns.in6.sa.scope_id = 0;
1728 }
1729 sl = @sizeOf(posix.sockaddr.in6);
1730 }
1731
1732 // Get local address and open/bind a socket
1733 var sa: Address = undefined;
1734 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1735 sa.any.family = family;
1736 try posix.bind(fd, &sa.any, sl);
1737
1738 var pfd = [1]posix.pollfd{posix.pollfd{
1739 .fd = fd,
1740 .events = posix.POLL.IN,
1741 .revents = undefined,
1742 }};
1743 const retry_interval = timeout / attempts;
1744 var next: u32 = 0;
1745 var t2: u64 = @bitCast(std.time.milliTimestamp());
1746 const t0 = t2;
1747 var t1 = t2 - retry_interval;
1748
1749 var servfail_retry: usize = undefined;
1750
1751 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
1752 if (t2 - t1 >= retry_interval) {
1753 // Query all configured nameservers in parallel
1754 var i: usize = 0;
1755 while (i < queries.len) : (i += 1) {
1756 if (answers[i].len == 0) {
1757 for (ns_list.items) |*ns| {
1758 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1759 }
1760 }
1761 }
1762 t1 = t2;
1763 servfail_retry = 2 * queries.len;
1764 }
1765
1766 // Wait for a response, or until time to retry
1767 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1768 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1769 if (nevents == 0) continue;
1770
1771 while (true) {
1772 var sl_copy = sl;
1773 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1774
1775 // Ignore non-identifiable packets
1776 if (rlen < 4) continue;
1777
1778 // Ignore replies from addresses we didn't send to
1779 const ns = for (ns_list.items) |*ns| {
1780 if (ns.eql(sa)) break ns;
1781 } else continue;
1782
1783 // Find which query this answer goes with, if any
1784 var i: usize = next;
1785 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1786 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1787 {}
1788
1789 if (i == queries.len) continue;
1790 if (answers[i].len != 0) continue;
1791
1792 // Only accept positive or negative responses;
1793 // retry immediately on server failure, and ignore
1794 // all other codes such as refusal.
1795 switch (answer_bufs[next][3] & 15) {
1796 0, 3 => {},
1797 2 => if (servfail_retry != 0) {
1798 servfail_retry -= 1;
1799 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1800 },
1801 else => continue,
1802 }
1803
1804 // Store answer in the right slot, or update next
1805 // available temp slot if it's already in place.
1806 answers[i].len = rlen;
1807 if (i == next) {
1808 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1809 } else {
1810 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1811 }
1812
1813 if (next == queries.len) break :outer;
1814 }
1815 }
1816 }
1817
1818 fn deinit(rc: *ResolvConf) void {
1819 const gpa = rc.gpa;
1820 rc.ns.deinit(gpa);
1821 rc.search.deinit(gpa);
1822 rc.* = undefined;
1823 }
1824};
1825
1826fn linuxLookupNameFromNumericUnspec(
1827 gpa: Allocator,
1828 addrs: *ArrayList(LookupAddr),
1829 name: []const u8,
1830 port: u16,
1831) !void {
1832 const addr = try Address.resolveIp(name, port);
1833 try addrs.append(gpa, .{ .addr = addr });
1834}
1835
1836fn dnsParse(
1837 r: []const u8,
1838 ctx: anytype,
1839 comptime callback: anytype,
1840) !void {
1841 // This implementation is ported from musl libc.
1842 // A more idiomatic "ziggy" implementation would be welcome.
1843 if (r.len < 12) return error.InvalidDnsPacket;
1844 if ((r[3] & 15) != 0) return;
1845 var p = r.ptr + 12;
1846 var qdcount = r[4] * @as(usize, 256) + r[5];
1847 var ancount = r[6] * @as(usize, 256) + r[7];
1848 if (qdcount + ancount > 64) return error.InvalidDnsPacket;
1849 while (qdcount != 0) {
1850 qdcount -= 1;
1851 while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
1852 if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6)
1853 return error.InvalidDnsPacket;
1854 p += @as(usize, 5) + @intFromBool(p[0] != 0);
1855 }
1856 while (ancount != 0) {
1857 ancount -= 1;
1858 while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
1859 if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6)
1860 return error.InvalidDnsPacket;
1861 p += @as(usize, 1) + @intFromBool(p[0] != 0);
1862 const len = p[8] * @as(usize, 256) + p[9];
1863 if (@intFromPtr(p) + len > @intFromPtr(r.ptr) + r.len) return error.InvalidDnsPacket;
1864 try callback(ctx, p[1], p[10..][0..len], r);
1865 p += 10 + len;
1866 }
1867}
1868
1869fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1870 const gpa = ctx.gpa;
1871 switch (rr) {
1872 posix.RR.A => {
1873 if (data.len != 4) return error.InvalidDnsARecord;
1874 try ctx.addrs.append(gpa, .{
1875 .addr = Address.initIp4(data[0..4].*, ctx.port),
1876 });
1877 },
1878 posix.RR.AAAA => {
1879 if (data.len != 16) return error.InvalidDnsAAAARecord;
1880 try ctx.addrs.append(gpa, .{
1881 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1882 });
1883 },
1884 posix.RR.CNAME => {
1885 var tmp: [256]u8 = undefined;
1886 // Returns len of compressed name. strlen to get canon name.
1887 _ = try posix.dn_expand(packet, data, &tmp);
1888 const canon_name = mem.sliceTo(&tmp, 0);
1889 if (isValidHostName(canon_name)) {
1890 ctx.canon.items.len = 0;
1891 try ctx.canon.appendSlice(gpa, canon_name);
1892 }
1893 },
1894 else => return,
1895 }
1896}
1897
1898pub const Stream = struct {
1899 /// Underlying platform-defined type which may or may not be
1900 /// interchangeable with a file system file descriptor.
1901 handle: Handle,
1902
1903 pub const Handle = switch (native_os) {
1904 .windows => windows.ws2_32.SOCKET,
1905 else => posix.fd_t,
1906 };
1907
1908 pub fn close(s: Stream) void {
1909 switch (native_os) {
1910 .windows => windows.closesocket(s.handle) catch unreachable,
1911 else => posix.close(s.handle),
1912 }
1913 }
1914
1915 pub const ReadError = posix.ReadError || error{
1916 SocketNotBound,
1917 MessageTooBig,
1918 NetworkSubsystemFailed,
1919 ConnectionResetByPeer,
1920 SocketUnconnected,
1921 };
1922
1923 pub const WriteError = posix.SendMsgError || error{
1924 ConnectionResetByPeer,
1925 SocketNotBound,
1926 MessageTooBig,
1927 NetworkSubsystemFailed,
1928 SystemResources,
1929 SocketUnconnected,
1930 Unexpected,
1931 };
1932
1933 pub const Reader = switch (native_os) {
1934 .windows => struct {
1935 /// Use `interface` for portable code.
1936 interface_state: Io.Reader,
1937 /// Use `getStream` for portable code.
1938 net_stream: Stream,
1939 /// Use `getError` for portable code.
1940 error_state: ?Error,
1941
1942 pub const Error = ReadError;
1943
1944 pub fn getStream(r: *const Reader) Stream {
1945 return r.net_stream;
1946 }
1947
1948 pub fn getError(r: *const Reader) ?Error {
1949 return r.error_state;
1950 }
1951
1952 pub fn interface(r: *Reader) *Io.Reader {
1953 return &r.interface_state;
1954 }
1955
1956 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1957 return .{
1958 .interface_state = .{
1959 .vtable = &.{
1960 .stream = stream,
1961 .readVec = readVec,
1962 },
1963 .buffer = buffer,
1964 .seek = 0,
1965 .end = 0,
1966 },
1967 .net_stream = net_stream,
1968 .error_state = null,
1969 };
1970 }
1971
1972 fn stream(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
1973 const dest = limit.slice(try io_w.writableSliceGreedy(1));
1974 var bufs: [1][]u8 = .{dest};
1975 const n = try readVec(io_r, &bufs);
1976 io_w.advance(n);
1977 return n;
1978 }
1979
1980 fn readVec(io_r: *std.Io.Reader, data: [][]u8) Io.Reader.Error!usize {
1981 const r: *Reader = @alignCast(@fieldParentPtr("interface_state", io_r));
1982 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
1983 const bufs_n, const data_size = try io_r.writableVectorWsa(&iovecs, data);
1984 const bufs = iovecs[0..bufs_n];
1985 assert(bufs[0].len != 0);
1986 const n = streamBufs(r, bufs) catch |err| {
1987 r.error_state = err;
1988 return error.ReadFailed;
1989 };
1990 if (n == 0) return error.EndOfStream;
1991 if (n > data_size) {
1992 io_r.end += n - data_size;
1993 return data_size;
1994 }
1995 return n;
1996 }
1997
1998 fn handleRecvError(winsock_error: windows.ws2_32.WinsockError) Error!void {
1999 switch (winsock_error) {
2000 .WSAECONNRESET => return error.ConnectionResetByPeer,
2001 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2002 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2003 .WSAEINVAL => return error.SocketNotBound,
2004 .WSAEMSGSIZE => return error.MessageTooBig,
2005 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2006 .WSAENETRESET => return error.ConnectionResetByPeer,
2007 .WSAENOTCONN => return error.SocketUnconnected,
2008 .WSAEWOULDBLOCK => return error.WouldBlock,
2009 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2010 .WSA_IO_PENDING => unreachable,
2011 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2012 else => |err| return windows.unexpectedWSAError(err),
2013 }
2014 }
2015
2016 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2017 var flags: u32 = 0;
2018 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2019
2020 var n: u32 = undefined;
2021 if (windows.ws2_32.WSARecv(
2022 r.net_stream.handle,
2023 bufs.ptr,
2024 @intCast(bufs.len),
2025 &n,
2026 &flags,
2027 &overlapped,
2028 null,
2029 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2030 .WSA_IO_PENDING => {
2031 var result_flags: u32 = undefined;
2032 if (windows.ws2_32.WSAGetOverlappedResult(
2033 r.net_stream.handle,
2034 &overlapped,
2035 &n,
2036 windows.TRUE,
2037 &result_flags,
2038 ) == windows.FALSE) try handleRecvError(windows.ws2_32.WSAGetLastError());
2039 },
2040 else => |winsock_error| try handleRecvError(winsock_error),
2041 };
2042
2043 return n;
2044 }
2045 },
2046 else => struct {
2047 /// Use `getStream`, `interface`, and `getError` for portable code.
2048 file_reader: File.Reader,
2049
2050 pub const Error = ReadError;
2051
2052 pub fn interface(r: *Reader) *Io.Reader {
2053 return &r.file_reader.interface;
2054 }
2055
2056 pub fn init(net_stream: Stream, buffer: []u8) Reader {
2057 return .{
2058 .file_reader = .{
2059 .interface = File.Reader.initInterface(buffer),
2060 .file = .{ .handle = net_stream.handle },
2061 .mode = .streaming,
2062 .seek_err = error.Unseekable,
2063 .size_err = error.Streaming,
2064 },
2065 };
2066 }
2067
2068 pub fn getStream(r: *const Reader) Stream {
2069 return .{ .handle = r.file_reader.file.handle };
2070 }
2071
2072 pub fn getError(r: *const Reader) ?Error {
2073 return r.file_reader.err;
2074 }
2075 },
2076 };
2077
2078 pub const Writer = switch (native_os) {
2079 .windows => struct {
2080 /// This field is present on all systems.
2081 interface: Io.Writer,
2082 /// Use `getStream` for cross-platform support.
2083 stream: Stream,
2084 /// This field is present on all systems.
2085 err: ?Error = null,
2086
2087 pub const Error = WriteError;
2088
2089 pub fn init(stream: Stream, buffer: []u8) Writer {
2090 return .{
2091 .stream = stream,
2092 .interface = .{
2093 .vtable = &.{ .drain = drain },
2094 .buffer = buffer,
2095 },
2096 };
2097 }
2098
2099 pub fn getStream(w: *const Writer) Stream {
2100 return w.stream;
2101 }
2102
2103 fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
2104 const cap = std.math.maxInt(u32);
2105 var remaining = bytes;
2106 while (remaining.len > cap) {
2107 if (v.len - i.* == 0) return;
2108 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap };
2109 i.* += 1;
2110 remaining = remaining[cap..];
2111 } else {
2112 @branchHint(.likely);
2113 if (v.len - i.* == 0) return;
2114 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) };
2115 i.* += 1;
2116 }
2117 }
2118
2119 fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
2120 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2121 const buffered = io_w.buffered();
2122 comptime assert(native_os == .windows);
2123 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
2124 var len: u32 = 0;
2125 addWsaBuf(&iovecs, &len, buffered);
2126 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes);
2127 const pattern = data[data.len - 1];
2128 if (iovecs.len - len != 0) switch (splat) {
2129 0 => {},
2130 1 => addWsaBuf(&iovecs, &len, pattern),
2131 else => switch (pattern.len) {
2132 0 => {},
2133 1 => {
2134 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2135 var backup_buffer: [64]u8 = undefined;
2136 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2137 splat_buffer_candidate
2138 else
2139 &backup_buffer;
2140 const memset_len = @min(splat_buffer.len, splat);
2141 const buf = splat_buffer[0..memset_len];
2142 @memset(buf, pattern[0]);
2143 addWsaBuf(&iovecs, &len, buf);
2144 var remaining_splat = splat - buf.len;
2145 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2146 addWsaBuf(&iovecs, &len, splat_buffer);
2147 remaining_splat -= splat_buffer.len;
2148 }
2149 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
2150 },
2151 else => for (0..@min(splat, iovecs.len - len)) |_| {
2152 addWsaBuf(&iovecs, &len, pattern);
2153 },
2154 },
2155 };
2156 const n = sendBufs(w.stream.handle, iovecs[0..len]) catch |err| {
2157 w.err = err;
2158 return error.WriteFailed;
2159 };
2160 return io_w.consume(n);
2161 }
2162
2163 fn handleSendError(winsock_error: windows.ws2_32.WinsockError) Error!void {
2164 switch (winsock_error) {
2165 .WSAECONNABORTED => return error.ConnectionResetByPeer,
2166 .WSAECONNRESET => return error.ConnectionResetByPeer,
2167 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2168 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2169 .WSAEINVAL => return error.SocketNotBound,
2170 .WSAEMSGSIZE => return error.MessageTooBig,
2171 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2172 .WSAENETRESET => return error.ConnectionResetByPeer,
2173 .WSAENOBUFS => return error.SystemResources,
2174 .WSAENOTCONN => return error.SocketUnconnected,
2175 .WSAENOTSOCK => unreachable, // not a socket
2176 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
2177 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
2178 .WSAEWOULDBLOCK => return error.WouldBlock,
2179 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2180 .WSA_IO_PENDING => unreachable,
2181 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2182 else => |err| return windows.unexpectedWSAError(err),
2183 }
2184 }
2185
2186 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2187 var n: u32 = undefined;
2188 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2189 if (windows.ws2_32.WSASend(
2190 handle,
2191 bufs.ptr,
2192 @intCast(bufs.len),
2193 &n,
2194 0,
2195 &overlapped,
2196 null,
2197 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2198 .WSA_IO_PENDING => {
2199 var result_flags: u32 = undefined;
2200 if (windows.ws2_32.WSAGetOverlappedResult(
2201 handle,
2202 &overlapped,
2203 &n,
2204 windows.TRUE,
2205 &result_flags,
2206 ) == windows.FALSE) try handleSendError(windows.ws2_32.WSAGetLastError());
2207 },
2208 else => |winsock_error| try handleSendError(winsock_error),
2209 };
2210
2211 return n;
2212 }
2213 },
2214 else => struct {
2215 /// This field is present on all systems.
2216 interface: Io.Writer,
2217
2218 err: ?Error = null,
2219 file_writer: File.Writer,
2220
2221 pub const Error = WriteError;
2222
2223 pub fn init(stream: Stream, buffer: []u8) Writer {
2224 return .{
2225 .interface = .{
2226 .vtable = &.{
2227 .drain = drain,
2228 .sendFile = sendFile,
2229 },
2230 .buffer = buffer,
2231 },
2232 .file_writer = .initStreaming(.{ .handle = stream.handle }, &.{}),
2233 };
2234 }
2235
2236 pub fn getStream(w: *const Writer) Stream {
2237 return .{ .handle = w.file_writer.file.handle };
2238 }
2239
2240 fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
2241 // OS checks ptr addr before length so zero length vectors must be omitted.
2242 if (bytes.len == 0) return;
2243 if (v.len - i.* == 0) return;
2244 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
2245 i.* += 1;
2246 }
2247
2248 fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
2249 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2250 const buffered = io_w.buffered();
2251 var iovecs: [max_buffers_len]posix.iovec_const = undefined;
2252 var msg: posix.msghdr_const = .{
2253 .name = null,
2254 .namelen = 0,
2255 .iov = &iovecs,
2256 .iovlen = 0,
2257 .control = null,
2258 .controllen = 0,
2259 .flags = 0,
2260 };
2261 addBuf(&iovecs, &msg.iovlen, buffered);
2262 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
2263 const pattern = data[data.len - 1];
2264 if (iovecs.len - msg.iovlen != 0) switch (splat) {
2265 0 => {},
2266 1 => addBuf(&iovecs, &msg.iovlen, pattern),
2267 else => switch (pattern.len) {
2268 0 => {},
2269 1 => {
2270 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2271 var backup_buffer: [64]u8 = undefined;
2272 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2273 splat_buffer_candidate
2274 else
2275 &backup_buffer;
2276 const memset_len = @min(splat_buffer.len, splat);
2277 const buf = splat_buffer[0..memset_len];
2278 @memset(buf, pattern[0]);
2279 addBuf(&iovecs, &msg.iovlen, buf);
2280 var remaining_splat = splat - buf.len;
2281 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2282 assert(buf.len == splat_buffer.len);
2283 addBuf(&iovecs, &msg.iovlen, splat_buffer);
2284 remaining_splat -= splat_buffer.len;
2285 }
2286 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
2287 },
2288 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
2289 addBuf(&iovecs, &msg.iovlen, pattern);
2290 },
2291 },
2292 };
2293 const flags = posix.MSG.NOSIGNAL;
2294 return io_w.consume(posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
2295 w.err = err;
2296 return error.WriteFailed;
2297 });
2298 }
2299
2300 fn sendFile(io_w: *Io.Writer, file_reader: *File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
2301 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2302 const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit);
2303 return io_w.consume(n);
2304 }
2305 },
2306 };
2307
2308 pub fn reader(stream: Stream, buffer: []u8) Reader {
2309 return .init(stream, buffer);
2310 }
2311
2312 pub fn writer(stream: Stream, buffer: []u8) Writer {
2313 return .init(stream, buffer);
2314 }
2315
2316 const max_buffers_len = 8;
2317
2318 /// Deprecated in favor of `Reader`.
2319 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
2320 if (native_os == .windows) {
2321 return windows.ReadFile(self.handle, buffer, null);
2322 }
2323
2324 return posix.read(self.handle, buffer);
2325 }
2326
2327 /// Deprecated in favor of `Reader`.
2328 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
2329 if (native_os == .windows) {
2330 if (iovecs.len == 0) return 0;
2331 const first = iovecs[0];
2332 return windows.ReadFile(s.handle, first.base[0..first.len], null);
2333 }
2334
2335 return posix.readv(s.handle, iovecs);
2336 }
2337
2338 /// Deprecated in favor of `Reader`.
2339 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
2340 assert(len <= buffer.len);
2341 var index: usize = 0;
2342 while (index < len) {
2343 const amt = try s.read(buffer[index..]);
2344 if (amt == 0) break;
2345 index += amt;
2346 }
2347 return index;
2348 }
2349
2350 /// Deprecated in favor of `Writer`.
2351 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
2352 var stream_writer = self.writer(&.{});
2353 return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?;
2354 }
2355
2356 /// Deprecated in favor of `Writer`.
2357 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
2358 var index: usize = 0;
2359 while (index < bytes.len) {
2360 index += try self.write(bytes[index..]);
2361 }
2362 }
2363
2364 /// Deprecated in favor of `Writer`.
2365 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
2366 return @errorCast(posix.writev(self.handle, iovecs));
2367 }
2368
2369 /// Deprecated in favor of `Writer`.
2370 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
2371 if (iovecs.len == 0) return;
2372
2373 var i: usize = 0;
2374 while (true) {
2375 var amt = try self.writev(iovecs[i..]);
2376 while (amt >= iovecs[i].len) {
2377 amt -= iovecs[i].len;
2378 i += 1;
2379 if (i >= iovecs.len) return;
2380 }
2381 iovecs[i].base += amt;
2382 iovecs[i].len -= amt;
2383 }
2384 }
2385};
2386
2387/// A bound, listening TCP socket, ready to accept new connections.
2388pub const Server = struct {
2389 listen_address: Address,
2390 stream: Stream,
2391
2392 pub const Connection = struct {
2393 stream: Stream,
2394 address: Address,
2395 };
2396
2397 pub fn deinit(s: *Server) void {
2398 s.stream.close();
2399 s.* = undefined;
2400 }
2401
2402 pub const AcceptError = posix.AcceptError;
2403
2404 /// Blocks until a client connects to the server. The returned `Connection` has
2405 /// an open stream.
2406 pub fn accept(s: *Server) AcceptError!Connection {
2407 var accepted_addr: Address = undefined;
2408 var addr_len: posix.socklen_t = @sizeOf(Address);
2409 const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC);
2410 return .{
2411 .stream = .{ .handle = fd },
2412 .address = accepted_addr,
2413 };
2414 }
2415};
2416
2417test {
2418 if (builtin.os.tag != .wasi) {
2419 _ = Server;
2420 _ = Stream;
2421 _ = Address;
2422 _ = @import("net/test.zig");
2423 }
2424}
lib/std/net/test.zig deleted-373
......@@ -1,373 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const net = std.net;
4const mem = std.mem;
5const testing = std.testing;
6
7test "parse and render IP addresses at comptime" {
8 comptime {
9 const ipv6addr = net.Address.parseIp("::1", 0) catch unreachable;
10 try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
11
12 const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable;
13 try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
14
15 try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("::123.123.123.123", 0));
16 try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("127.01.0.1", 0));
17 try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("::123.123.123.123", 0));
18 try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("127.01.0.1", 0));
19 }
20}
21
22test "format IPv6 address with no zero runs" {
23 const addr = try std.net.Address.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});
25}
26
27test "parse IPv6 addresses and check compressed form" {
28 try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
29 try std.net.Address.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
30 });
31 try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
32 try std.net.Address.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
33 });
34 try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
35 try std.net.Address.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
36 });
37}
38
39test "parse IPv6 address, check raw bytes" {
40 const expected_raw: [16]u8 = .{
41 0x20, 0x01, 0x0d, 0xb8, // 2001:db8
42 0x00, 0x00, 0x00, 0x00, // :0000:0000
43 0x00, 0x01, 0x00, 0x00, // :0001:0000
44 0x00, 0x00, 0x00, 0x02, // :0000:0002
45 };
46
47 const addr = try std.net.Address.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);
51}
52
53test "parse and render IPv6 addresses" {
54 var buffer: [100]u8 = undefined;
55 const ips = [_][]const u8{
56 "FF01:0:0:0:0:0:0:FB",
57 "FF01::Fb",
58 "::1",
59 "::",
60 "1::",
61 "2001:db8::",
62 "::1234:5678",
63 "2001:db8::1234:5678",
64 "FF01::FB%1234",
65 "::ffff:123.5.123.5",
66 };
67 const printed = [_][]const u8{
68 "ff01::fb",
69 "ff01::fb",
70 "::1",
71 "::",
72 "1::",
73 "2001:db8::",
74 "::1234:5678",
75 "2001:db8::1234:5678",
76 "ff01::fb%1234",
77 "::ffff:123.5.123.5",
78 };
79 for (ips, 0..) |ip, i| {
80 const addr = net.Address.parseIp6(ip, 0) catch unreachable;
81 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]));
83
84 if (builtin.os.tag == .linux) {
85 const addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
86 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]));
88 }
89 }
90
91 try testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
92 try testing.expectError(error.Overflow, net.Address.parseIp6("FF001::FB", 0));
93 try testing.expectError(error.InvalidCharacter, net.Address.parseIp6("FF01::Fb:zig", 0));
94 try testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
95 try testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
96 try testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
97 try testing.expectError(error.Incomplete, net.Address.parseIp6("1", 0));
98 // TODO Make this test pass on other operating systems.
99 if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) {
100 try testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
101 // Assumes IFNAMESIZE will always be a multiple of 2
102 try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));
103 try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
104 }
105}
106
107test "invalid but parseable IPv6 scope ids" {
108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
110 // TODO Make this test pass on other operating systems.
111 return error.SkipZigTest;
112 }
113
114 try testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
115}
116
117test "parse and render IPv4 addresses" {
118 var buffer: [18]u8 = undefined;
119 for ([_][]const u8{
120 "0.0.0.0",
121 "255.255.255.255",
122 "1.2.3.4",
123 "123.255.0.91",
124 "127.0.0.1",
125 }) |ip| {
126 const addr = net.Address.parseIp4(ip, 0) catch unreachable;
127 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]));
129 }
130
131 try testing.expectError(error.Overflow, net.Address.parseIp4("256.0.0.1", 0));
132 try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("x.0.0.1", 0));
133 try testing.expectError(error.InvalidEnd, net.Address.parseIp4("127.0.0.1.1", 0));
134 try testing.expectError(error.Incomplete, net.Address.parseIp4("127.0.0.", 0));
135 try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("100..0.1", 0));
136 try testing.expectError(error.NonCanonical, net.Address.parseIp4("127.01.0.1", 0));
137}
138
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
150test "resolve DNS" {
151 if (builtin.os.tag == .wasi) return error.SkipZigTest;
152
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 }
161
162 // Resolve localhost, this should not fail.
163 {
164 const localhost_v4 = try net.Address.parseIp("127.0.0.1", 80);
165 const localhost_v6 = try net.Address.parseIp("::2", 80);
166
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;
171 } else @panic("unexpected address for localhost");
172 }
173
174 {
175 // The tests are required to work even when there is no Internet connection,
176 // 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) {
178 error.UnknownHostName => return error.SkipZigTest,
179 error.TemporaryNameServerFailure => return error.SkipZigTest,
180 else => return err,
181 };
182 result.deinit();
183 }
184}
185
186test "listen on a port, send bytes, receive bytes" {
187 if (builtin.single_threaded) return error.SkipZigTest;
188 if (builtin.os.tag == .wasi) return error.SkipZigTest;
189
190 if (builtin.os.tag == .windows) {
191 _ = try std.os.windows.WSAStartup(2, 2);
192 }
193 defer {
194 if (builtin.os.tag == .windows) {
195 std.os.windows.WSACleanup() catch unreachable;
196 }
197 }
198
199 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
200 // configured.
201 const localhost = try net.Address.parseIp("127.0.0.1", 0);
202
203 var server = try localhost.listen(.{});
204 defer server.deinit();
205
206 const S = struct {
207 fn clientFn(server_address: net.Address) !void {
208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();
210
211 var stream_writer = socket.writer(&.{});
212 try stream_writer.interface.writeAll("Hello world!");
213 }
214 };
215
216 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
217 defer t.join();
218
219 var client = try server.accept();
220 defer client.stream.close();
221 var buf: [16]u8 = undefined;
222 var stream_reader = client.stream.reader(&.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);
224
225 try testing.expectEqual(@as(usize, 12), n);
226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
227}
228
229test "listen on an in use port" {
230 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
231 // TODO build abstractions for other operating systems
232 return error.SkipZigTest;
233 }
234
235 const localhost = try net.Address.parseIp("127.0.0.1", 0);
236
237 var server1 = try localhost.listen(.{ .reuse_address = true });
238 defer server1.deinit();
239
240 var server2 = try server1.listen_address.listen(.{ .reuse_address = true });
241 defer server2.deinit();
242}
243
244fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
245 if (builtin.os.tag == .wasi) return error.SkipZigTest;
246
247 const connection = try net.tcpConnectToHost(allocator, name, port);
248 defer connection.close();
249
250 var buf: [100]u8 = undefined;
251 const len = try connection.read(&buf);
252 const msg = buf[0..len];
253 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
254}
255
256fn testClient(addr: net.Address) anyerror!void {
257 if (builtin.os.tag == .wasi) return error.SkipZigTest;
258
259 const socket_file = try net.tcpConnectToAddress(addr);
260 defer socket_file.close();
261
262 var buf: [100]u8 = undefined;
263 const len = try socket_file.read(&buf);
264 const msg = buf[0..len];
265 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
266}
267
268fn testServer(server: *net.Server) anyerror!void {
269 if (builtin.os.tag == .wasi) return error.SkipZigTest;
270
271 var client = try server.accept();
272
273 const stream = client.stream.writer();
274 try stream.print("hello from server\n", .{});
275}
276
277test "listen on a unix socket, send bytes, receive bytes" {
278 if (builtin.single_threaded) return error.SkipZigTest;
279 if (!net.has_unix_sockets) return error.SkipZigTest;
280
281 if (builtin.os.tag == .windows) {
282 _ = try std.os.windows.WSAStartup(2, 2);
283 }
284 defer {
285 if (builtin.os.tag == .windows) {
286 std.os.windows.WSACleanup() catch unreachable;
287 }
288 }
289
290 const socket_path = try generateFileName("socket.unix");
291 defer testing.allocator.free(socket_path);
292
293 const socket_addr = try net.Address.initUnix(socket_path);
294 defer std.fs.cwd().deleteFile(socket_path) catch {};
295
296 var server = try socket_addr.listen(.{});
297 defer server.deinit();
298
299 const S = struct {
300 fn clientFn(path: []const u8) !void {
301 const socket = try net.connectUnixSocket(path);
302 defer socket.close();
303
304 var stream_writer = socket.writer(&.{});
305 try stream_writer.interface.writeAll("Hello world!");
306 }
307 };
308
309 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
310 defer t.join();
311
312 var client = try server.accept();
313 defer client.stream.close();
314 var buf: [16]u8 = undefined;
315 var stream_reader = client.stream.reader(&.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);
317
318 try testing.expectEqual(@as(usize, 12), n);
319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
320}
321
322test "listen on a unix socket with reuse_address option" {
323 if (!net.has_unix_sockets) return error.SkipZigTest;
324 // Windows doesn't implement reuse port option.
325 if (builtin.os.tag == .windows) return error.SkipZigTest;
326
327 const socket_path = try generateFileName("socket.unix");
328 defer testing.allocator.free(socket_path);
329
330 const socket_addr = try net.Address.initUnix(socket_path);
331 defer std.fs.cwd().deleteFile(socket_path) catch {};
332
333 var server = try socket_addr.listen(.{ .reuse_address = true });
334 server.deinit();
335}
336
337fn generateFileName(base_name: []const u8) ![]const u8 {
338 const random_bytes_count = 12;
339 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
340 var random_bytes: [12]u8 = undefined;
341 std.crypto.random.bytes(&random_bytes);
342 var sub_path: [sub_path_len]u8 = undefined;
343 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
344 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
345}
346
347test "non-blocking tcp server" {
348 if (builtin.os.tag == .wasi) return error.SkipZigTest;
349 if (true) {
350 // https://github.com/ziglang/zig/issues/18315
351 return error.SkipZigTest;
352 }
353
354 const localhost = try net.Address.parseIp("127.0.0.1", 0);
355 var server = localhost.listen(.{ .force_nonblocking = true });
356 defer server.deinit();
357
358 const accept_err = server.accept();
359 try testing.expectError(error.WouldBlock, accept_err);
360
361 const socket_file = try net.tcpConnectToAddress(server.listen_address);
362 defer socket_file.close();
363
364 var client = try server.accept();
365 defer client.stream.close();
366 const stream = client.stream.writer();
367 try stream.print("hello from server\n", .{});
368
369 var buf: [100]u8 = undefined;
370 const len = try socket_file.read(&buf);
371 const msg = buf[0..len];
372 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
373}
lib/std/std.zig-1
......@@ -85,7 +85,6 @@ pub const macho = @import("macho.zig");
8585pub const math = @import("math.zig");
8686pub const mem = @import("mem.zig");
8787pub const meta = @import("meta.zig");
88pub const net = @import("net.zig");
8988pub const os = @import("os.zig");
9089pub const once = @import("once.zig").once;
9190pub const pdb = @import("pdb.zig");