authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-15 01:42:40+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-15 01:42:40+02:00
log148befdaa32cb42837f83733b4f299be86743d71
tree363119c1b8430966a2260a16640b932bb1160e17
parentf3f2a56859f96cee6f9bc8e8fe14b99ec653abaf
parente4ebbdb354f5d562f7fca025f57e4775d9c23882
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24409 from ziglang/net

std.net: update to new I/O API

13 files changed, 1132 insertions(+), 675 deletions(-)

lib/std/Build/Cache.zig+2-1
...@@ -661,7 +661,8 @@ pub const Manifest = struct {...@@ -661,7 +661,8 @@ pub const Manifest = struct {
661 } {661 } {
662 const gpa = self.cache.gpa;662 const gpa = self.cache.gpa;
663 const input_file_count = self.files.entries.len;663 const input_file_count = self.files.entries.len;
664 var manifest_reader = self.manifest_file.?.reader(&.{}); // Reads positionally from zero.664 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
665 var manifest_reader = self.manifest_file.?.reader(&tiny_buffer); // Reads positionally from zero.
665 const limit: std.io.Limit = .limited(manifest_file_size_max);666 const limit: std.io.Limit = .limited(manifest_file_size_max);
666 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {667 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
667 error.OutOfMemory => return error.OutOfMemory,668 error.OutOfMemory => return error.OutOfMemory,
lib/std/Io.zig+27-1
...@@ -312,6 +312,32 @@ pub fn GenericReader(...@@ -312,6 +312,32 @@ pub fn GenericReader(
312 const ptr: *const Context = @alignCast(@ptrCast(context));312 const ptr: *const Context = @alignCast(@ptrCast(context));
313 return readFn(ptr.*, buffer);313 return readFn(ptr.*, buffer);
314 }314 }
315
316 /// Helper for bridging to the new `Reader` API while upgrading.
317 pub fn adaptToNewApi(self: *const Self) Adapter {
318 return .{
319 .derp_reader = self.*,
320 .new_interface = .{
321 .buffer = &.{},
322 .vtable = &.{ .stream = Adapter.stream },
323 },
324 };
325 }
326
327 pub const Adapter = struct {
328 derp_reader: Self,
329 new_interface: Reader,
330 err: ?Error = null,
331
332 fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
333 const a: *@This() = @alignCast(@fieldParentPtr("new_interface", r));
334 const buf = limit.slice(try w.writableSliceGreedy(1));
335 return a.derp_reader.read(buf) catch |err| {
336 a.err = err;
337 return error.ReadFailed;
338 };
339 }
340 };
315 };341 };
316}342}
317343
...@@ -393,7 +419,7 @@ pub fn GenericWriter(...@@ -393,7 +419,7 @@ pub fn GenericWriter(
393419
394 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {420 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
395 _ = splat;421 _ = splat;
396 const a: *@This() = @fieldParentPtr("new_interface", w);422 const a: *@This() = @alignCast(@fieldParentPtr("new_interface", w));
397 return a.derp_writer.write(data[0]) catch |err| {423 return a.derp_writer.write(data[0]) catch |err| {
398 a.err = err;424 a.err = err;
399 return error.WriteFailed;425 return error.WriteFailed;
lib/std/Io/Reader.zig+3-1
...@@ -245,6 +245,7 @@ pub fn appendRemaining(...@@ -245,6 +245,7 @@ pub fn appendRemaining(
245 list: *std.ArrayListAlignedUnmanaged(u8, alignment),245 list: *std.ArrayListAlignedUnmanaged(u8, alignment),
246 limit: Limit,246 limit: Limit,
247) LimitedAllocError!void {247) LimitedAllocError!void {
248 assert(r.buffer.len != 0); // Needed to detect limit exceeded without losing data.
248 const buffer = r.buffer;249 const buffer = r.buffer;
249 const buffer_contents = buffer[r.seek..r.end];250 const buffer_contents = buffer[r.seek..r.end];
250 const copy_len = limit.minInt(buffer_contents.len);251 const copy_len = limit.minInt(buffer_contents.len);
...@@ -1657,11 +1658,12 @@ test "readAlloc when the backing reader provides one byte at a time" {...@@ -1657,11 +1658,12 @@ test "readAlloc when the backing reader provides one byte at a time" {
1657 }1658 }
1658 };1659 };
1659 const str = "This is a test";1660 const str = "This is a test";
1661 var tiny_buffer: [1]u8 = undefined;
1660 var one_byte_stream: OneByteReader = .{1662 var one_byte_stream: OneByteReader = .{
1661 .str = str,1663 .str = str,
1662 .i = 0,1664 .i = 0,
1663 .reader = .{1665 .reader = .{
1664 .buffer = &.{},1666 .buffer = &tiny_buffer,
1665 .vtable = &.{ .stream = OneByteReader.stream },1667 .vtable = &.{ .stream = OneByteReader.stream },
1666 .seek = 0,1668 .seek = 0,
1667 .end = 0,1669 .end = 0,
lib/std/Io/Writer.zig+27-1
...@@ -393,6 +393,32 @@ pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit)...@@ -393,6 +393,32 @@ pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit)
393 return buffer[0..i];393 return buffer[0..i];
394}394}
395395
396pub fn writableVectorWsa(
397 w: *Writer,
398 buffer: []std.os.windows.ws2_32.WSABUF,
399 limit: Limit,
400) Error![]std.os.windows.ws2_32.WSABUF {
401 var it = try writableVectorIterator(w);
402 var i: usize = 0;
403 var remaining = limit;
404 while (it.next()) |full_buffer| {
405 if (!remaining.nonzero()) break;
406 if (buffer.len - i == 0) break;
407 const buf = remaining.slice(full_buffer);
408 if (buf.len == 0) continue;
409 if (std.math.cast(u32, buf.len)) |len| {
410 buffer[i] = .{ .buf = buf.ptr, .len = len };
411 i += 1;
412 remaining = remaining.subtract(len).?;
413 continue;
414 }
415 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };
416 i += 1;
417 break;
418 }
419 return buffer[0..i];
420}
421
396pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {422pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
397 _ = try writableSliceGreedy(w, n);423 _ = try writableSliceGreedy(w, n);
398}424}
...@@ -2162,7 +2188,7 @@ pub const Discarding = struct {...@@ -2162,7 +2188,7 @@ pub const Discarding = struct {
2162 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {2188 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2163 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));2189 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2164 const slice = data[0 .. data.len - 1];2190 const slice = data[0 .. data.len - 1];
2165 const pattern = data[slice.len..];2191 const pattern = data[slice.len];
2166 var written: usize = pattern.len * splat;2192 var written: usize = pattern.len * splat;
2167 for (slice) |bytes| written += bytes.len;2193 for (slice) |bytes| written += bytes.len;
2168 d.count += w.end + written;2194 d.count += w.end + written;
lib/std/c.zig+39-2
...@@ -4110,7 +4110,7 @@ pub const msghdr_const = switch (native_os) {...@@ -4110,7 +4110,7 @@ pub const msghdr_const = switch (native_os) {
4110 /// scatter/gather array4110 /// scatter/gather array
4111 iov: [*]const iovec_const,4111 iov: [*]const iovec_const,
4112 /// # elements in iov4112 /// # elements in iov
4113 iovlen: i32,4113 iovlen: u32,
4114 /// ancillary data4114 /// ancillary data
4115 control: ?*const anyopaque,4115 control: ?*const anyopaque,
4116 /// ancillary data buffer len4116 /// ancillary data buffer len
...@@ -4122,7 +4122,7 @@ pub const msghdr_const = switch (native_os) {...@@ -4122,7 +4122,7 @@ pub const msghdr_const = switch (native_os) {
4122 name: ?*const anyopaque,4122 name: ?*const anyopaque,
4123 namelen: socklen_t,4123 namelen: socklen_t,
4124 iov: [*]const iovec,4124 iov: [*]const iovec,
4125 iovlen: c_int,4125 iovlen: c_uint,
4126 control: ?*const anyopaque,4126 control: ?*const anyopaque,
4127 controllen: socklen_t,4127 controllen: socklen_t,
4128 flags: c_int,4128 flags: c_int,
...@@ -5625,6 +5625,43 @@ pub const MSG = switch (native_os) {...@@ -5625,6 +5625,43 @@ pub const MSG = switch (native_os) {
5625 pub const NOSIGNAL = 0x80;5625 pub const NOSIGNAL = 0x80;
5626 pub const EOR = 0x100;5626 pub const EOR = 0x100;
5627 },5627 },
5628 .freebsd => struct {
5629 pub const OOB = 0x00000001;
5630 pub const PEEK = 0x00000002;
5631 pub const DONTROUTE = 0x00000004;
5632 pub const EOR = 0x00000008;
5633 pub const TRUNC = 0x00000010;
5634 pub const CTRUNC = 0x00000020;
5635 pub const WAITALL = 0x00000040;
5636 pub const DONTWAIT = 0x00000080;
5637 pub const EOF = 0x00000100;
5638 pub const NOTIFICATION = 0x00002000;
5639 pub const NBIO = 0x00004000;
5640 pub const COMPAT = 0x00008000;
5641 pub const SOCALLBCK = 0x00010000;
5642 pub const NOSIGNAL = 0x00020000;
5643 pub const CMSG_CLOEXEC = 0x00040000;
5644 pub const WAITFORONE = 0x00080000;
5645 pub const MORETOCOME = 0x00100000;
5646 pub const TLSAPPDATA = 0x00200000;
5647 },
5648 .netbsd => struct {
5649 pub const OOB = 0x0001;
5650 pub const PEEK = 0x0002;
5651 pub const DONTROUTE = 0x0004;
5652 pub const EOR = 0x0008;
5653 pub const TRUNC = 0x0010;
5654 pub const CTRUNC = 0x0020;
5655 pub const WAITALL = 0x0040;
5656 pub const DONTWAIT = 0x0080;
5657 pub const BCAST = 0x0100;
5658 pub const MCAST = 0x0200;
5659 pub const NOSIGNAL = 0x0400;
5660 pub const CMSG_CLOEXEC = 0x0800;
5661 pub const NBIO = 0x1000;
5662 pub const WAITFORONE = 0x2000;
5663 pub const NOTIFICATION = 0x4000;
5664 },
5628 else => void,5665 else => void,
5629};5666};
5630pub const SOCK = switch (native_os) {5667pub const SOCK = switch (native_os) {
lib/std/crypto.zig+2-4
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1//! Cryptography.1//! Cryptography.
22
3const root = @import("root");3const std = @import("std.zig");
44
5pub const timing_safe = @import("crypto/timing_safe.zig");5pub const timing_safe = @import("crypto/timing_safe.zig");
66
...@@ -118,7 +118,7 @@ pub const hash = struct {...@@ -118,7 +118,7 @@ pub const hash = struct {
118 pub const blake2 = @import("crypto/blake2.zig");118 pub const blake2 = @import("crypto/blake2.zig");
119 pub const Blake3 = @import("crypto/blake3.zig").Blake3;119 pub const Blake3 = @import("crypto/blake3.zig").Blake3;
120 pub const Md5 = @import("crypto/md5.zig").Md5;120 pub const Md5 = @import("crypto/md5.zig").Md5;
121 pub const Sha1 = @import("crypto/sha1.zig").Sha1;121 pub const Sha1 = @import("crypto/Sha1.zig");
122 pub const sha2 = @import("crypto/sha2.zig");122 pub const sha2 = @import("crypto/sha2.zig");
123 pub const sha3 = @import("crypto/sha3.zig");123 pub const sha3 = @import("crypto/sha3.zig");
124 pub const composition = @import("crypto/hash_composition.zig");124 pub const composition = @import("crypto/hash_composition.zig");
...@@ -216,8 +216,6 @@ pub const random = @import("crypto/tlcsprng.zig").interface;...@@ -216,8 +216,6 @@ pub const random = @import("crypto/tlcsprng.zig").interface;
216/// Encoding and decoding216/// Encoding and decoding
217pub const codecs = @import("crypto/codecs.zig");217pub const codecs = @import("crypto/codecs.zig");
218218
219const std = @import("std.zig");
220
221pub const errors = @import("crypto/errors.zig");219pub const errors = @import("crypto/errors.zig");
222220
223pub const tls = @import("crypto/tls.zig");221pub const tls = @import("crypto/tls.zig");
lib/std/crypto/Sha1.zig created+306
...@@ -0,0 +1,306 @@
1//! The SHA-1 function is now considered cryptographically broken.
2//! Namely, it is feasible to find multiple inputs producing the same hash.
3//! For a fast-performing, cryptographically secure hash function, see SHA512/256, BLAKE2 or BLAKE3.
4
5const std = @import("../std.zig");
6const mem = std.mem;
7const math = std.math;
8const Sha1 = @This();
9
10pub const block_length = 64;
11pub const digest_length = 20;
12pub const Options = struct {};
13
14s: [5]u32,
15/// Streaming Cache
16buf: [64]u8 = undefined,
17buf_len: u8 = 0,
18total_len: u64 = 0,
19
20pub fn init(options: Options) Sha1 {
21 _ = options;
22 return .{
23 .s = [_]u32{
24 0x67452301,
25 0xEFCDAB89,
26 0x98BADCFE,
27 0x10325476,
28 0xC3D2E1F0,
29 },
30 };
31}
32
33pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
34 var d = Sha1.init(options);
35 d.update(b);
36 d.final(out);
37}
38
39pub fn update(d: *Sha1, b: []const u8) void {
40 var off: usize = 0;
41
42 // Partial buffer exists from previous update. Copy into buffer then hash.
43 if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
44 off += 64 - d.buf_len;
45 @memcpy(d.buf[d.buf_len..][0..off], b[0..off]);
46
47 d.round(d.buf[0..]);
48 d.buf_len = 0;
49 }
50
51 // Full middle blocks.
52 while (off + 64 <= b.len) : (off += 64) {
53 d.round(b[off..][0..64]);
54 }
55
56 // Copy any remainder for next pass.
57 @memcpy(d.buf[d.buf_len..][0 .. b.len - off], b[off..]);
58 d.buf_len += @as(u8, @intCast(b[off..].len));
59
60 d.total_len += b.len;
61}
62
63pub fn peek(d: Sha1) [digest_length]u8 {
64 var copy = d;
65 return copy.finalResult();
66}
67
68pub fn final(d: *Sha1, out: *[digest_length]u8) void {
69 // The buffer here will never be completely full.
70 @memset(d.buf[d.buf_len..], 0);
71
72 // Append padding bits.
73 d.buf[d.buf_len] = 0x80;
74 d.buf_len += 1;
75
76 // > 448 mod 512 so need to add an extra round to wrap around.
77 if (64 - d.buf_len < 8) {
78 d.round(d.buf[0..]);
79 @memset(d.buf[0..], 0);
80 }
81
82 // Append message length.
83 var i: usize = 1;
84 var len = d.total_len >> 5;
85 d.buf[63] = @as(u8, @intCast(d.total_len & 0x1f)) << 3;
86 while (i < 8) : (i += 1) {
87 d.buf[63 - i] = @as(u8, @intCast(len & 0xff));
88 len >>= 8;
89 }
90
91 d.round(d.buf[0..]);
92
93 for (d.s, 0..) |s, j| {
94 mem.writeInt(u32, out[4 * j ..][0..4], s, .big);
95 }
96}
97
98pub fn finalResult(d: *Sha1) [digest_length]u8 {
99 var result: [digest_length]u8 = undefined;
100 d.final(&result);
101 return result;
102}
103
104fn round(d: *Sha1, b: *const [64]u8) void {
105 var s: [16]u32 = undefined;
106
107 var v: [5]u32 = [_]u32{
108 d.s[0],
109 d.s[1],
110 d.s[2],
111 d.s[3],
112 d.s[4],
113 };
114
115 const round0a = comptime [_]RoundParam{
116 .abcdei(0, 1, 2, 3, 4, 0),
117 .abcdei(4, 0, 1, 2, 3, 1),
118 .abcdei(3, 4, 0, 1, 2, 2),
119 .abcdei(2, 3, 4, 0, 1, 3),
120 .abcdei(1, 2, 3, 4, 0, 4),
121 .abcdei(0, 1, 2, 3, 4, 5),
122 .abcdei(4, 0, 1, 2, 3, 6),
123 .abcdei(3, 4, 0, 1, 2, 7),
124 .abcdei(2, 3, 4, 0, 1, 8),
125 .abcdei(1, 2, 3, 4, 0, 9),
126 .abcdei(0, 1, 2, 3, 4, 10),
127 .abcdei(4, 0, 1, 2, 3, 11),
128 .abcdei(3, 4, 0, 1, 2, 12),
129 .abcdei(2, 3, 4, 0, 1, 13),
130 .abcdei(1, 2, 3, 4, 0, 14),
131 .abcdei(0, 1, 2, 3, 4, 15),
132 };
133 inline for (round0a) |r| {
134 s[r.i] = mem.readInt(u32, b[r.i * 4 ..][0..4], .big);
135
136 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
137 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
138 }
139
140 const round0b = comptime [_]RoundParam{
141 .abcdei(4, 0, 1, 2, 3, 16),
142 .abcdei(3, 4, 0, 1, 2, 17),
143 .abcdei(2, 3, 4, 0, 1, 18),
144 .abcdei(1, 2, 3, 4, 0, 19),
145 };
146 inline for (round0b) |r| {
147 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
148 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
149
150 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
151 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
152 }
153
154 const round1 = comptime [_]RoundParam{
155 .abcdei(0, 1, 2, 3, 4, 20),
156 .abcdei(4, 0, 1, 2, 3, 21),
157 .abcdei(3, 4, 0, 1, 2, 22),
158 .abcdei(2, 3, 4, 0, 1, 23),
159 .abcdei(1, 2, 3, 4, 0, 24),
160 .abcdei(0, 1, 2, 3, 4, 25),
161 .abcdei(4, 0, 1, 2, 3, 26),
162 .abcdei(3, 4, 0, 1, 2, 27),
163 .abcdei(2, 3, 4, 0, 1, 28),
164 .abcdei(1, 2, 3, 4, 0, 29),
165 .abcdei(0, 1, 2, 3, 4, 30),
166 .abcdei(4, 0, 1, 2, 3, 31),
167 .abcdei(3, 4, 0, 1, 2, 32),
168 .abcdei(2, 3, 4, 0, 1, 33),
169 .abcdei(1, 2, 3, 4, 0, 34),
170 .abcdei(0, 1, 2, 3, 4, 35),
171 .abcdei(4, 0, 1, 2, 3, 36),
172 .abcdei(3, 4, 0, 1, 2, 37),
173 .abcdei(2, 3, 4, 0, 1, 38),
174 .abcdei(1, 2, 3, 4, 0, 39),
175 };
176 inline for (round1) |r| {
177 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
178 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
179
180 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x6ED9EBA1 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
181 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
182 }
183
184 const round2 = comptime [_]RoundParam{
185 .abcdei(0, 1, 2, 3, 4, 40),
186 .abcdei(4, 0, 1, 2, 3, 41),
187 .abcdei(3, 4, 0, 1, 2, 42),
188 .abcdei(2, 3, 4, 0, 1, 43),
189 .abcdei(1, 2, 3, 4, 0, 44),
190 .abcdei(0, 1, 2, 3, 4, 45),
191 .abcdei(4, 0, 1, 2, 3, 46),
192 .abcdei(3, 4, 0, 1, 2, 47),
193 .abcdei(2, 3, 4, 0, 1, 48),
194 .abcdei(1, 2, 3, 4, 0, 49),
195 .abcdei(0, 1, 2, 3, 4, 50),
196 .abcdei(4, 0, 1, 2, 3, 51),
197 .abcdei(3, 4, 0, 1, 2, 52),
198 .abcdei(2, 3, 4, 0, 1, 53),
199 .abcdei(1, 2, 3, 4, 0, 54),
200 .abcdei(0, 1, 2, 3, 4, 55),
201 .abcdei(4, 0, 1, 2, 3, 56),
202 .abcdei(3, 4, 0, 1, 2, 57),
203 .abcdei(2, 3, 4, 0, 1, 58),
204 .abcdei(1, 2, 3, 4, 0, 59),
205 };
206 inline for (round2) |r| {
207 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
208 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
209
210 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x8F1BBCDC +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) ^ (v[r.b] & v[r.d]) ^ (v[r.c] & v[r.d]));
211 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
212 }
213
214 const round3 = comptime [_]RoundParam{
215 .abcdei(0, 1, 2, 3, 4, 60),
216 .abcdei(4, 0, 1, 2, 3, 61),
217 .abcdei(3, 4, 0, 1, 2, 62),
218 .abcdei(2, 3, 4, 0, 1, 63),
219 .abcdei(1, 2, 3, 4, 0, 64),
220 .abcdei(0, 1, 2, 3, 4, 65),
221 .abcdei(4, 0, 1, 2, 3, 66),
222 .abcdei(3, 4, 0, 1, 2, 67),
223 .abcdei(2, 3, 4, 0, 1, 68),
224 .abcdei(1, 2, 3, 4, 0, 69),
225 .abcdei(0, 1, 2, 3, 4, 70),
226 .abcdei(4, 0, 1, 2, 3, 71),
227 .abcdei(3, 4, 0, 1, 2, 72),
228 .abcdei(2, 3, 4, 0, 1, 73),
229 .abcdei(1, 2, 3, 4, 0, 74),
230 .abcdei(0, 1, 2, 3, 4, 75),
231 .abcdei(4, 0, 1, 2, 3, 76),
232 .abcdei(3, 4, 0, 1, 2, 77),
233 .abcdei(2, 3, 4, 0, 1, 78),
234 .abcdei(1, 2, 3, 4, 0, 79),
235 };
236 inline for (round3) |r| {
237 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
238 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
239
240 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0xCA62C1D6 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
241 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
242 }
243
244 d.s[0] +%= v[0];
245 d.s[1] +%= v[1];
246 d.s[2] +%= v[2];
247 d.s[3] +%= v[3];
248 d.s[4] +%= v[4];
249}
250
251const RoundParam = struct {
252 a: usize,
253 b: usize,
254 c: usize,
255 d: usize,
256 e: usize,
257 i: u32,
258
259 fn abcdei(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam {
260 return .{
261 .a = a,
262 .b = b,
263 .c = c,
264 .d = d,
265 .e = e,
266 .i = i,
267 };
268 }
269};
270
271const htest = @import("test.zig");
272
273test "sha1 single" {
274 try htest.assertEqualHash(Sha1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "");
275 try htest.assertEqualHash(Sha1, "a9993e364706816aba3e25717850c26c9cd0d89d", "abc");
276 try htest.assertEqualHash(Sha1, "a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
277}
278
279test "sha1 streaming" {
280 var h = Sha1.init(.{});
281 var out: [20]u8 = undefined;
282
283 h.final(&out);
284 try htest.assertEqual("da39a3ee5e6b4b0d3255bfef95601890afd80709", out[0..]);
285
286 h = Sha1.init(.{});
287 h.update("abc");
288 h.final(&out);
289 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
290
291 h = Sha1.init(.{});
292 h.update("a");
293 h.update("b");
294 h.update("c");
295 h.final(&out);
296 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
297}
298
299test "sha1 aligned final" {
300 var block = [_]u8{0} ** Sha1.block_length;
301 var out: [Sha1.digest_length]u8 = undefined;
302
303 var h = Sha1.init(.{});
304 h.update(&block);
305 h.final(out[0..]);
306}
lib/std/crypto/sha1.zig deleted-319
...@@ -1,319 +0,0 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3const math = std.math;
4
5const RoundParam = struct {
6 a: usize,
7 b: usize,
8 c: usize,
9 d: usize,
10 e: usize,
11 i: u32,
12};
13
14fn roundParam(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam {
15 return RoundParam{
16 .a = a,
17 .b = b,
18 .c = c,
19 .d = d,
20 .e = e,
21 .i = i,
22 };
23}
24
25/// The SHA-1 function is now considered cryptographically broken.
26/// Namely, it is feasible to find multiple inputs producing the same hash.
27/// For a fast-performing, cryptographically secure hash function, see SHA512/256, BLAKE2 or BLAKE3.
28pub const Sha1 = struct {
29 const Self = @This();
30 pub const block_length = 64;
31 pub const digest_length = 20;
32 pub const Options = struct {};
33
34 s: [5]u32,
35 // Streaming Cache
36 buf: [64]u8 = undefined,
37 buf_len: u8 = 0,
38 total_len: u64 = 0,
39
40 pub fn init(options: Options) Self {
41 _ = options;
42 return Self{
43 .s = [_]u32{
44 0x67452301,
45 0xEFCDAB89,
46 0x98BADCFE,
47 0x10325476,
48 0xC3D2E1F0,
49 },
50 };
51 }
52
53 pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
54 var d = Sha1.init(options);
55 d.update(b);
56 d.final(out);
57 }
58
59 pub fn update(d: *Self, b: []const u8) void {
60 var off: usize = 0;
61
62 // Partial buffer exists from previous update. Copy into buffer then hash.
63 if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
64 off += 64 - d.buf_len;
65 @memcpy(d.buf[d.buf_len..][0..off], b[0..off]);
66
67 d.round(d.buf[0..]);
68 d.buf_len = 0;
69 }
70
71 // Full middle blocks.
72 while (off + 64 <= b.len) : (off += 64) {
73 d.round(b[off..][0..64]);
74 }
75
76 // Copy any remainder for next pass.
77 @memcpy(d.buf[d.buf_len..][0 .. b.len - off], b[off..]);
78 d.buf_len += @as(u8, @intCast(b[off..].len));
79
80 d.total_len += b.len;
81 }
82
83 pub fn peek(d: Self) [digest_length]u8 {
84 var copy = d;
85 return copy.finalResult();
86 }
87
88 pub fn final(d: *Self, out: *[digest_length]u8) void {
89 // The buffer here will never be completely full.
90 @memset(d.buf[d.buf_len..], 0);
91
92 // Append padding bits.
93 d.buf[d.buf_len] = 0x80;
94 d.buf_len += 1;
95
96 // > 448 mod 512 so need to add an extra round to wrap around.
97 if (64 - d.buf_len < 8) {
98 d.round(d.buf[0..]);
99 @memset(d.buf[0..], 0);
100 }
101
102 // Append message length.
103 var i: usize = 1;
104 var len = d.total_len >> 5;
105 d.buf[63] = @as(u8, @intCast(d.total_len & 0x1f)) << 3;
106 while (i < 8) : (i += 1) {
107 d.buf[63 - i] = @as(u8, @intCast(len & 0xff));
108 len >>= 8;
109 }
110
111 d.round(d.buf[0..]);
112
113 for (d.s, 0..) |s, j| {
114 mem.writeInt(u32, out[4 * j ..][0..4], s, .big);
115 }
116 }
117
118 pub fn finalResult(d: *Self) [digest_length]u8 {
119 var result: [digest_length]u8 = undefined;
120 d.final(&result);
121 return result;
122 }
123
124 fn round(d: *Self, b: *const [64]u8) void {
125 var s: [16]u32 = undefined;
126
127 var v: [5]u32 = [_]u32{
128 d.s[0],
129 d.s[1],
130 d.s[2],
131 d.s[3],
132 d.s[4],
133 };
134
135 const round0a = comptime [_]RoundParam{
136 roundParam(0, 1, 2, 3, 4, 0),
137 roundParam(4, 0, 1, 2, 3, 1),
138 roundParam(3, 4, 0, 1, 2, 2),
139 roundParam(2, 3, 4, 0, 1, 3),
140 roundParam(1, 2, 3, 4, 0, 4),
141 roundParam(0, 1, 2, 3, 4, 5),
142 roundParam(4, 0, 1, 2, 3, 6),
143 roundParam(3, 4, 0, 1, 2, 7),
144 roundParam(2, 3, 4, 0, 1, 8),
145 roundParam(1, 2, 3, 4, 0, 9),
146 roundParam(0, 1, 2, 3, 4, 10),
147 roundParam(4, 0, 1, 2, 3, 11),
148 roundParam(3, 4, 0, 1, 2, 12),
149 roundParam(2, 3, 4, 0, 1, 13),
150 roundParam(1, 2, 3, 4, 0, 14),
151 roundParam(0, 1, 2, 3, 4, 15),
152 };
153 inline for (round0a) |r| {
154 s[r.i] = mem.readInt(u32, b[r.i * 4 ..][0..4], .big);
155
156 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
157 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
158 }
159
160 const round0b = comptime [_]RoundParam{
161 roundParam(4, 0, 1, 2, 3, 16),
162 roundParam(3, 4, 0, 1, 2, 17),
163 roundParam(2, 3, 4, 0, 1, 18),
164 roundParam(1, 2, 3, 4, 0, 19),
165 };
166 inline for (round0b) |r| {
167 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
168 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
169
170 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
171 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
172 }
173
174 const round1 = comptime [_]RoundParam{
175 roundParam(0, 1, 2, 3, 4, 20),
176 roundParam(4, 0, 1, 2, 3, 21),
177 roundParam(3, 4, 0, 1, 2, 22),
178 roundParam(2, 3, 4, 0, 1, 23),
179 roundParam(1, 2, 3, 4, 0, 24),
180 roundParam(0, 1, 2, 3, 4, 25),
181 roundParam(4, 0, 1, 2, 3, 26),
182 roundParam(3, 4, 0, 1, 2, 27),
183 roundParam(2, 3, 4, 0, 1, 28),
184 roundParam(1, 2, 3, 4, 0, 29),
185 roundParam(0, 1, 2, 3, 4, 30),
186 roundParam(4, 0, 1, 2, 3, 31),
187 roundParam(3, 4, 0, 1, 2, 32),
188 roundParam(2, 3, 4, 0, 1, 33),
189 roundParam(1, 2, 3, 4, 0, 34),
190 roundParam(0, 1, 2, 3, 4, 35),
191 roundParam(4, 0, 1, 2, 3, 36),
192 roundParam(3, 4, 0, 1, 2, 37),
193 roundParam(2, 3, 4, 0, 1, 38),
194 roundParam(1, 2, 3, 4, 0, 39),
195 };
196 inline for (round1) |r| {
197 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
198 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
199
200 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x6ED9EBA1 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
201 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
202 }
203
204 const round2 = comptime [_]RoundParam{
205 roundParam(0, 1, 2, 3, 4, 40),
206 roundParam(4, 0, 1, 2, 3, 41),
207 roundParam(3, 4, 0, 1, 2, 42),
208 roundParam(2, 3, 4, 0, 1, 43),
209 roundParam(1, 2, 3, 4, 0, 44),
210 roundParam(0, 1, 2, 3, 4, 45),
211 roundParam(4, 0, 1, 2, 3, 46),
212 roundParam(3, 4, 0, 1, 2, 47),
213 roundParam(2, 3, 4, 0, 1, 48),
214 roundParam(1, 2, 3, 4, 0, 49),
215 roundParam(0, 1, 2, 3, 4, 50),
216 roundParam(4, 0, 1, 2, 3, 51),
217 roundParam(3, 4, 0, 1, 2, 52),
218 roundParam(2, 3, 4, 0, 1, 53),
219 roundParam(1, 2, 3, 4, 0, 54),
220 roundParam(0, 1, 2, 3, 4, 55),
221 roundParam(4, 0, 1, 2, 3, 56),
222 roundParam(3, 4, 0, 1, 2, 57),
223 roundParam(2, 3, 4, 0, 1, 58),
224 roundParam(1, 2, 3, 4, 0, 59),
225 };
226 inline for (round2) |r| {
227 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
228 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
229
230 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x8F1BBCDC +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) ^ (v[r.b] & v[r.d]) ^ (v[r.c] & v[r.d]));
231 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
232 }
233
234 const round3 = comptime [_]RoundParam{
235 roundParam(0, 1, 2, 3, 4, 60),
236 roundParam(4, 0, 1, 2, 3, 61),
237 roundParam(3, 4, 0, 1, 2, 62),
238 roundParam(2, 3, 4, 0, 1, 63),
239 roundParam(1, 2, 3, 4, 0, 64),
240 roundParam(0, 1, 2, 3, 4, 65),
241 roundParam(4, 0, 1, 2, 3, 66),
242 roundParam(3, 4, 0, 1, 2, 67),
243 roundParam(2, 3, 4, 0, 1, 68),
244 roundParam(1, 2, 3, 4, 0, 69),
245 roundParam(0, 1, 2, 3, 4, 70),
246 roundParam(4, 0, 1, 2, 3, 71),
247 roundParam(3, 4, 0, 1, 2, 72),
248 roundParam(2, 3, 4, 0, 1, 73),
249 roundParam(1, 2, 3, 4, 0, 74),
250 roundParam(0, 1, 2, 3, 4, 75),
251 roundParam(4, 0, 1, 2, 3, 76),
252 roundParam(3, 4, 0, 1, 2, 77),
253 roundParam(2, 3, 4, 0, 1, 78),
254 roundParam(1, 2, 3, 4, 0, 79),
255 };
256 inline for (round3) |r| {
257 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
258 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
259
260 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0xCA62C1D6 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
261 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
262 }
263
264 d.s[0] +%= v[0];
265 d.s[1] +%= v[1];
266 d.s[2] +%= v[2];
267 d.s[3] +%= v[3];
268 d.s[4] +%= v[4];
269 }
270
271 pub const Error = error{};
272 pub const Writer = std.io.GenericWriter(*Self, Error, write);
273
274 fn write(self: *Self, bytes: []const u8) Error!usize {
275 self.update(bytes);
276 return bytes.len;
277 }
278
279 pub fn writer(self: *Self) Writer {
280 return .{ .context = self };
281 }
282};
283
284const htest = @import("test.zig");
285
286test "sha1 single" {
287 try htest.assertEqualHash(Sha1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "");
288 try htest.assertEqualHash(Sha1, "a9993e364706816aba3e25717850c26c9cd0d89d", "abc");
289 try htest.assertEqualHash(Sha1, "a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
290}
291
292test "sha1 streaming" {
293 var h = Sha1.init(.{});
294 var out: [20]u8 = undefined;
295
296 h.final(&out);
297 try htest.assertEqual("da39a3ee5e6b4b0d3255bfef95601890afd80709", out[0..]);
298
299 h = Sha1.init(.{});
300 h.update("abc");
301 h.final(&out);
302 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
303
304 h = Sha1.init(.{});
305 h.update("a");
306 h.update("b");
307 h.update("c");
308 h.final(&out);
309 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
310}
311
312test "sha1 aligned final" {
313 var block = [_]u8{0} ** Sha1.block_length;
314 var out: [Sha1.digest_length]u8 = undefined;
315
316 var h = Sha1.init(.{});
317 h.update(&block);
318 h.final(out[0..]);
319}
lib/std/fs/File.zig+2-1
...@@ -1369,7 +1369,7 @@ pub const Reader = struct {...@@ -1369,7 +1369,7 @@ pub const Reader = struct {
1369 r.pos = offset;1369 r.pos = offset;
1370 },1370 },
1371 .streaming, .streaming_reading => {1371 .streaming, .streaming_reading => {
1372 if (offset >= r.pos) return Reader.seekBy(r, offset - r.pos);1372 if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos));
1373 if (r.seek_err) |err| return err;1373 if (r.seek_err) |err| return err;
1374 posix.lseek_SET(r.file.handle, offset) catch |err| {1374 posix.lseek_SET(r.file.handle, offset) catch |err| {
1375 r.seek_err = err;1375 r.seek_err = err;
...@@ -1657,6 +1657,7 @@ pub const Writer = struct {...@@ -1657,6 +1657,7 @@ pub const Writer = struct {
1657 .file = w.file,1657 .file = w.file,
1658 .mode = w.mode,1658 .mode = w.mode,
1659 .pos = w.pos,1659 .pos = w.pos,
1660 .interface = Reader.initInterface(w.interface.buffer),
1660 .seek_err = w.seek_err,1661 .seek_err = w.seek_err,
1661 };1662 };
1662 }1663 }
lib/std/http/test.zig+15-6
...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -135,7 +135,8 @@ test "HTTP server handles a chunked transfer coding request" {
135 const gpa = std.testing.allocator;135 const gpa = std.testing.allocator;
136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());136 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
137 defer stream.close();137 defer stream.close();
138 try stream.writeAll(request_bytes);138 var stream_writer = stream.writer(&.{});
139 try stream_writer.interface.writeAll(request_bytes);
139140
140 const expected_response =141 const expected_response =
141 "HTTP/1.1 200 OK\r\n" ++142 "HTTP/1.1 200 OK\r\n" ++
...@@ -144,7 +145,9 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -144,7 +145,9 @@ test "HTTP server handles a chunked transfer coding request" {
144 "content-type: text/plain\r\n" ++145 "content-type: text/plain\r\n" ++
145 "\r\n" ++146 "\r\n" ++
146 "message from server!\n";147 "message from server!\n";
147 const response = try stream.reader().readAllAlloc(gpa, expected_response.len);148 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
149 var stream_reader = stream.reader(&tiny_buffer);
150 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len));
148 defer gpa.free(response);151 defer gpa.free(response);
149 try expectEqualStrings(expected_response, response);152 try expectEqualStrings(expected_response, response);
150}153}
...@@ -276,9 +279,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -276,9 +279,12 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
276 const gpa = std.testing.allocator;279 const gpa = std.testing.allocator;
277 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());280 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
278 defer stream.close();281 defer stream.close();
279 try stream.writeAll(request_bytes);282 var stream_writer = stream.writer(&.{});
283 try stream_writer.interface.writeAll(request_bytes);
280284
281 const response = try stream.reader().readAllAlloc(gpa, 8192);285 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
286 var stream_reader = stream.reader(&tiny_buffer);
287 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));
282 defer gpa.free(response);288 defer gpa.free(response);
283289
284 var expected_response = std.ArrayList(u8).init(gpa);290 var expected_response = std.ArrayList(u8).init(gpa);
...@@ -339,9 +345,12 @@ test "receiving arbitrary http headers from the client" {...@@ -339,9 +345,12 @@ test "receiving arbitrary http headers from the client" {
339 const gpa = std.testing.allocator;345 const gpa = std.testing.allocator;
340 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());346 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
341 defer stream.close();347 defer stream.close();
342 try stream.writeAll(request_bytes);348 var stream_writer = stream.writer(&.{});
349 try stream_writer.interface.writeAll(request_bytes);
343350
344 const response = try stream.reader().readAllAlloc(gpa, 8192);351 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
352 var stream_reader = stream.reader(&tiny_buffer);
353 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));
345 defer gpa.free(response);354 defer gpa.free(response);
346355
347 var expected_response = std.ArrayList(u8).init(gpa);356 var expected_response = std.ArrayList(u8).init(gpa);
lib/std/net.zig+697-332
...@@ -11,11 +11,15 @@ const io = std.io;...@@ -11,11 +11,15 @@ const io = std.io;
11const native_endian = builtin.target.cpu.arch.endian();11const native_endian = builtin.target.cpu.arch.endian();
12const native_os = builtin.os.tag;12const native_os = builtin.os.tag;
13const windows = std.os.windows;13const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
1417
15// Windows 10 added support for unix sockets in build 17063, redstone 4 is the18// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
16// first release to support them.19// first release to support them.
17pub const has_unix_sockets = switch (native_os) {20pub const has_unix_sockets = switch (native_os) {
18 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,21 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
22 .wasi => false,
19 else => true,23 else => true,
20};24};
2125
...@@ -719,7 +723,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {...@@ -719,7 +723,7 @@ pub fn connectUnixSocket(path: []const u8) !Stream {
719 );723 );
720 errdefer Stream.close(.{ .handle = sockfd });724 errdefer Stream.close(.{ .handle = sockfd });
721725
722 var addr = try std.net.Address.initUnix(path);726 var addr = try Address.initUnix(path);
723 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());727 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
724728
725 return .{ .handle = sockfd };729 return .{ .handle = sockfd };
...@@ -787,7 +791,7 @@ pub const AddressList = struct {...@@ -787,7 +791,7 @@ pub const AddressList = struct {
787pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;791pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
788792
789/// All memory allocated with `allocator` will be freed before this function returns.793/// All memory allocated with `allocator` will be freed before this function returns.
790pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {794pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
791 const list = try getAddressList(allocator, name, port);795 const list = try getAddressList(allocator, name, port);
792 defer list.deinit();796 defer list.deinit();
793797
...@@ -818,9 +822,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {...@@ -818,9 +822,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
818 return Stream{ .handle = sockfd };822 return Stream{ .handle = sockfd };
819}823}
820824
821const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{825// TODO: Instead of having a massive error set, make the error set have categories, and then
822 // TODO: break this up into error sets from the various underlying functions826// store the sub-error as a diagnostic value.
823827const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
824 TemporaryNameServerFailure,828 TemporaryNameServerFailure,
825 NameServerFailure,829 NameServerFailure,
826 AddressFamilyNotSupported,830 AddressFamilyNotSupported,
...@@ -840,12 +844,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||...@@ -840,12 +844,13 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError ||
840844
841 InterfaceNotFound,845 InterfaceNotFound,
842 FileSystem,846 FileSystem,
847 ResolveConfParseFailed,
843};848};
844849
845/// Call `AddressList.deinit` on the result.850/// Call `AddressList.deinit` on the result.
846pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {851pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
847 const result = blk: {852 const result = blk: {
848 var arena = std.heap.ArenaAllocator.init(allocator);853 var arena = std.heap.ArenaAllocator.init(gpa);
849 errdefer arena.deinit();854 errdefer arena.deinit();
850855
851 const result = try arena.allocator().create(AddressList);856 const result = try arena.allocator().create(AddressList);
...@@ -860,11 +865,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -860,11 +865,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
860 errdefer result.deinit();865 errdefer result.deinit();
861866
862 if (native_os == .windows) {867 if (native_os == .windows) {
863 const name_c = try allocator.dupeZ(u8, name);868 const name_c = try gpa.dupeZ(u8, name);
864 defer allocator.free(name_c);869 defer gpa.free(name_c);
865870
866 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);871 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
867 defer allocator.free(port_c);872 defer gpa.free(port_c);
868873
869 const ws2_32 = windows.ws2_32;874 const ws2_32 = windows.ws2_32;
870 const hints: posix.addrinfo = .{875 const hints: posix.addrinfo = .{
...@@ -932,11 +937,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -932,11 +937,11 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
932 }937 }
933938
934 if (builtin.link_libc) {939 if (builtin.link_libc) {
935 const name_c = try allocator.dupeZ(u8, name);940 const name_c = try gpa.dupeZ(u8, name);
936 defer allocator.free(name_c);941 defer gpa.free(name_c);
937942
938 const port_c = try std.fmt.allocPrintSentinel(allocator, "{}", .{port}, 0);943 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
939 defer allocator.free(port_c);944 defer gpa.free(port_c);
940945
941 const hints: posix.addrinfo = .{946 const hints: posix.addrinfo = .{
942 .flags = .{ .NUMERICSERV = true },947 .flags = .{ .NUMERICSERV = true },
...@@ -999,17 +1004,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get...@@ -999,17 +1004,17 @@ pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) Get
9991004
1000 if (native_os == .linux) {1005 if (native_os == .linux) {
1001 const family = posix.AF.UNSPEC;1006 const family = posix.AF.UNSPEC;
1002 var lookup_addrs = std.ArrayList(LookupAddr).init(allocator);1007 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1003 defer lookup_addrs.deinit();1008 defer lookup_addrs.deinit(gpa);
10041009
1005 var canon = std.ArrayList(u8).init(arena);1010 var canon: ArrayList(u8) = .empty;
1006 defer canon.deinit();1011 defer canon.deinit(gpa);
10071012
1008 try linuxLookupName(&lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);1013 try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);
10091014
1010 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);1015 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
1011 if (canon.items.len != 0) {1016 if (canon.items.len != 0) {
1012 result.canon_name = try canon.toOwnedSlice();1017 result.canon_name = try arena.dupe(u8, canon.items);
1013 }1018 }
10141019
1015 for (lookup_addrs.items, 0..) |lookup_addr, i| {1020 for (lookup_addrs.items, 0..) |lookup_addr, i| {
...@@ -1036,8 +1041,9 @@ const DAS_PREFIX_SHIFT = 8;...@@ -1036,8 +1041,9 @@ const DAS_PREFIX_SHIFT = 8;
1036const DAS_ORDER_SHIFT = 0;1041const DAS_ORDER_SHIFT = 0;
10371042
1038fn linuxLookupName(1043fn linuxLookupName(
1039 addrs: *std.ArrayList(LookupAddr),1044 gpa: Allocator,
1040 canon: *std.ArrayList(u8),1045 addrs: *ArrayList(LookupAddr),
1046 canon: *ArrayList(u8),
1041 opt_name: ?[]const u8,1047 opt_name: ?[]const u8,
1042 family: posix.sa_family_t,1048 family: posix.sa_family_t,
1043 flags: posix.AI,1049 flags: posix.AI,
...@@ -1046,13 +1052,13 @@ fn linuxLookupName(...@@ -1046,13 +1052,13 @@ fn linuxLookupName(
1046 if (opt_name) |name| {1052 if (opt_name) |name| {
1047 // reject empty name and check len so it fits into temp bufs1053 // reject empty name and check len so it fits into temp bufs
1048 canon.items.len = 0;1054 canon.items.len = 0;
1049 try canon.appendSlice(name);1055 try canon.appendSlice(gpa, name);
1050 if (Address.parseExpectingFamily(name, family, port)) |addr| {1056 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1051 try addrs.append(LookupAddr{ .addr = addr });1057 try addrs.append(gpa, .{ .addr = addr });
1052 } else |name_err| if (flags.NUMERICHOST) {1058 } else |name_err| if (flags.NUMERICHOST) {
1053 return name_err;1059 return name_err;
1054 } else {1060 } else {
1055 try linuxLookupNameFromHosts(addrs, canon, name, family, port);1061 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
1056 if (addrs.items.len == 0) {1062 if (addrs.items.len == 0) {
1057 // RFC 6761 Section 6.3.31063 // RFC 6761 Section 6.3.3
1058 // Name resolution APIs and libraries SHOULD recognize localhost1064 // Name resolution APIs and libraries SHOULD recognize localhost
...@@ -1063,17 +1069,18 @@ fn linuxLookupName(...@@ -1063,17 +1069,18 @@ fn linuxLookupName(
1063 // Check for equal to "localhost(.)" or ends in ".localhost(.)"1069 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
1064 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";1070 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
1065 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {1071 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {
1066 try addrs.append(LookupAddr{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });1072 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1067 try addrs.append(LookupAddr{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });1073 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1068 return;1074 return;
1069 }1075 }
10701076
1071 try linuxLookupNameFromDnsSearch(addrs, canon, name, family, port);1077 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
1072 }1078 }
1073 }1079 }
1074 } else {1080 } else {
1075 try canon.resize(0);1081 try canon.resize(gpa, 0);
1076 try linuxLookupNameFromNull(addrs, family, flags, port);1082 try addrs.ensureUnusedCapacity(gpa, 2);
1083 linuxLookupNameFromNull(addrs, family, flags, port);
1077 }1084 }
1078 if (addrs.items.len == 0) return error.UnknownHostName;1085 if (addrs.items.len == 0) return error.UnknownHostName;
10791086
...@@ -1279,39 +1286,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {...@@ -1279,39 +1286,40 @@ fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
1279}1286}
12801287
1281fn linuxLookupNameFromNull(1288fn linuxLookupNameFromNull(
1282 addrs: *std.ArrayList(LookupAddr),1289 addrs: *ArrayList(LookupAddr),
1283 family: posix.sa_family_t,1290 family: posix.sa_family_t,
1284 flags: posix.AI,1291 flags: posix.AI,
1285 port: u16,1292 port: u16,
1286) !void {1293) void {
1287 if (flags.PASSIVE) {1294 if (flags.PASSIVE) {
1288 if (family != posix.AF.INET6) {1295 if (family != posix.AF.INET6) {
1289 (try addrs.addOne()).* = LookupAddr{1296 addrs.appendAssumeCapacity(.{
1290 .addr = Address.initIp4([1]u8{0} ** 4, port),1297 .addr = Address.initIp4([1]u8{0} ** 4, port),
1291 };1298 });
1292 }1299 }
1293 if (family != posix.AF.INET) {1300 if (family != posix.AF.INET) {
1294 (try addrs.addOne()).* = LookupAddr{1301 addrs.appendAssumeCapacity(.{
1295 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),1302 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1296 };1303 });
1297 }1304 }
1298 } else {1305 } else {
1299 if (family != posix.AF.INET6) {1306 if (family != posix.AF.INET6) {
1300 (try addrs.addOne()).* = LookupAddr{1307 addrs.appendAssumeCapacity(.{
1301 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),1308 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1302 };1309 });
1303 }1310 }
1304 if (family != posix.AF.INET) {1311 if (family != posix.AF.INET) {
1305 (try addrs.addOne()).* = LookupAddr{1312 addrs.appendAssumeCapacity(.{
1306 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),1313 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1307 };1314 });
1308 }1315 }
1309 }1316 }
1310}1317}
13111318
1312fn linuxLookupNameFromHosts(1319fn linuxLookupNameFromHosts(
1313 addrs: *std.ArrayList(LookupAddr),1320 gpa: Allocator,
1314 canon: *std.ArrayList(u8),1321 addrs: *ArrayList(LookupAddr),
1322 canon: *ArrayList(u8),
1315 name: []const u8,1323 name: []const u8,
1316 family: posix.sa_family_t,1324 family: posix.sa_family_t,
1317 port: u16,1325 port: u16,
...@@ -1325,18 +1333,36 @@ fn linuxLookupNameFromHosts(...@@ -1325,18 +1333,36 @@ fn linuxLookupNameFromHosts(
1325 };1333 };
1326 defer file.close();1334 defer file.close();
13271335
1328 var buffered_reader = std.io.bufferedReader(file.deprecatedReader());
1329 const reader = buffered_reader.reader();
1330 var line_buf: [512]u8 = undefined;1336 var line_buf: [512]u8 = undefined;
1331 while (reader.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {1337 var file_reader = file.reader(&line_buf);
1332 error.StreamTooLong => blk: {1338 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
1333 // Skip to the delimiter in the reader, to fix parsing1339 error.OutOfMemory => return error.OutOfMemory,
1334 try reader.skipUntilDelimiterOrEof('\n');1340 error.ReadFailed => return file_reader.err.?,
1335 // Use the truncated line. A truncated comment or hostname will be handled correctly.1341 };
1336 break :blk &line_buf;1342}
1337 },1343
1338 else => |e| return e,1344fn parseHosts(
1339 }) |line| {1345 gpa: Allocator,
1346 addrs: *ArrayList(LookupAddr),
1347 canon: *ArrayList(u8),
1348 name: []const u8,
1349 family: posix.sa_family_t,
1350 port: u16,
1351 br: *io.Reader,
1352) error{ OutOfMemory, ReadFailed }!void {
1353 while (true) {
1354 const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) {
1355 error.StreamTooLong => {
1356 // Skip lines that are too long.
1357 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {
1358 error.EndOfStream => break,
1359 error.ReadFailed => return error.ReadFailed,
1360 };
1361 continue;
1362 },
1363 error.ReadFailed => return error.ReadFailed,
1364 error.EndOfStream => break,
1365 };
1340 var split_it = mem.splitScalar(u8, line, '#');1366 var split_it = mem.splitScalar(u8, line, '#');
1341 const no_comment_line = split_it.first();1367 const no_comment_line = split_it.first();
13421368
...@@ -1360,17 +1386,36 @@ fn linuxLookupNameFromHosts(...@@ -1360,17 +1386,36 @@ fn linuxLookupNameFromHosts(
1360 error.NonCanonical,1386 error.NonCanonical,
1361 => continue,1387 => continue,
1362 };1388 };
1363 try addrs.append(LookupAddr{ .addr = addr });1389 try addrs.append(gpa, .{ .addr = addr });
13641390
1365 // first name is canonical name1391 // first name is canonical name
1366 const name_text = first_name_text.?;1392 const name_text = first_name_text.?;
1367 if (isValidHostName(name_text)) {1393 if (isValidHostName(name_text)) {
1368 canon.items.len = 0;1394 canon.items.len = 0;
1369 try canon.appendSlice(name_text);1395 try canon.appendSlice(gpa, name_text);
1370 }1396 }
1371 }1397 }
1372}1398}
13731399
1400test parseHosts {
1401 if (builtin.os.tag == .wasi) {
1402 // TODO parsing addresses should not have OS dependencies
1403 return error.SkipZigTest;
1404 }
1405 var reader: std.io.Reader = .fixed(
1406 \\127.0.0.1 localhost
1407 \\::1 localhost
1408 \\127.0.0.2 abcd
1409 );
1410 var addrs: ArrayList(LookupAddr) = .empty;
1411 defer addrs.deinit(std.testing.allocator);
1412 var canon: ArrayList(u8) = .empty;
1413 defer canon.deinit(std.testing.allocator);
1414 try parseHosts(std.testing.allocator, &addrs, &canon, "abcd", posix.AF.UNSPEC, 1234, &reader);
1415 try std.testing.expectEqual(1, addrs.items.len);
1416 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
1417}
1418
1374pub fn isValidHostName(hostname: []const u8) bool {1419pub fn isValidHostName(hostname: []const u8) bool {
1375 if (hostname.len >= 254) return false;1420 if (hostname.len >= 254) return false;
1376 if (!std.unicode.utf8ValidateSlice(hostname)) return false;1421 if (!std.unicode.utf8ValidateSlice(hostname)) return false;
...@@ -1384,14 +1429,15 @@ pub fn isValidHostName(hostname: []const u8) bool {...@@ -1384,14 +1429,15 @@ pub fn isValidHostName(hostname: []const u8) bool {
1384}1429}
13851430
1386fn linuxLookupNameFromDnsSearch(1431fn linuxLookupNameFromDnsSearch(
1387 addrs: *std.ArrayList(LookupAddr),1432 gpa: Allocator,
1388 canon: *std.ArrayList(u8),1433 addrs: *ArrayList(LookupAddr),
1434 canon: *ArrayList(u8),
1389 name: []const u8,1435 name: []const u8,
1390 family: posix.sa_family_t,1436 family: posix.sa_family_t,
1391 port: u16,1437 port: u16,
1392) !void {1438) !void {
1393 var rc: ResolvConf = undefined;1439 var rc: ResolvConf = undefined;
1394 try getResolvConf(addrs.allocator, &rc);1440 rc.init(gpa) catch return error.ResolveConfParseFailed;
1395 defer rc.deinit();1441 defer rc.deinit();
13961442
1397 // Count dots, suppress search when >=ndots or name ends in1443 // Count dots, suppress search when >=ndots or name ends in
...@@ -1416,37 +1462,40 @@ fn linuxLookupNameFromDnsSearch(...@@ -1416,37 +1462,40 @@ fn linuxLookupNameFromDnsSearch(
1416 // provides the desired default canonical name (if the requested1462 // provides the desired default canonical name (if the requested
1417 // name is not a CNAME record) and serves as a buffer for passing1463 // name is not a CNAME record) and serves as a buffer for passing
1418 // the full requested name to name_from_dns.1464 // the full requested name to name_from_dns.
1419 try canon.resize(canon_name.len);1465 try canon.resize(gpa, canon_name.len);
1420 @memcpy(canon.items, canon_name);1466 @memcpy(canon.items, canon_name);
1421 try canon.append('.');1467 try canon.append(gpa, '.');
14221468
1423 var tok_it = mem.tokenizeAny(u8, search, " \t");1469 var tok_it = mem.tokenizeAny(u8, search, " \t");
1424 while (tok_it.next()) |tok| {1470 while (tok_it.next()) |tok| {
1425 canon.shrinkRetainingCapacity(canon_name.len + 1);1471 canon.shrinkRetainingCapacity(canon_name.len + 1);
1426 try canon.appendSlice(tok);1472 try canon.appendSlice(gpa, tok);
1427 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);1473 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
1428 if (addrs.items.len != 0) return;1474 if (addrs.items.len != 0) return;
1429 }1475 }
14301476
1431 canon.shrinkRetainingCapacity(canon_name.len);1477 canon.shrinkRetainingCapacity(canon_name.len);
1432 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);1478 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
1433}1479}
14341480
1435const dpc_ctx = struct {1481const dpc_ctx = struct {
1436 addrs: *std.ArrayList(LookupAddr),1482 gpa: Allocator,
1437 canon: *std.ArrayList(u8),1483 addrs: *ArrayList(LookupAddr),
1484 canon: *ArrayList(u8),
1438 port: u16,1485 port: u16,
1439};1486};
14401487
1441fn linuxLookupNameFromDns(1488fn linuxLookupNameFromDns(
1442 addrs: *std.ArrayList(LookupAddr),1489 gpa: Allocator,
1443 canon: *std.ArrayList(u8),1490 addrs: *ArrayList(LookupAddr),
1491 canon: *ArrayList(u8),
1444 name: []const u8,1492 name: []const u8,
1445 family: posix.sa_family_t,1493 family: posix.sa_family_t,
1446 rc: ResolvConf,1494 rc: ResolvConf,
1447 port: u16,1495 port: u16,
1448) !void {1496) !void {
1449 const ctx = dpc_ctx{1497 const ctx: dpc_ctx = .{
1498 .gpa = gpa,
1450 .addrs = addrs,1499 .addrs = addrs,
1451 .canon = canon,1500 .canon = canon,
1452 .port = port,1501 .port = port,
...@@ -1456,8 +1505,8 @@ fn linuxLookupNameFromDns(...@@ -1456,8 +1505,8 @@ fn linuxLookupNameFromDns(
1456 rr: u8,1505 rr: u8,
1457 };1506 };
1458 const afrrs = [_]AfRr{1507 const afrrs = [_]AfRr{
1459 AfRr{ .af = posix.AF.INET6, .rr = posix.RR.A },1508 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1460 AfRr{ .af = posix.AF.INET, .rr = posix.RR.AAAA },1509 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1461 };1510 };
1462 var qbuf: [2][280]u8 = undefined;1511 var qbuf: [2][280]u8 = undefined;
1463 var abuf: [2][512]u8 = undefined;1512 var abuf: [2][512]u8 = undefined;
...@@ -1477,7 +1526,7 @@ fn linuxLookupNameFromDns(...@@ -1477,7 +1526,7 @@ fn linuxLookupNameFromDns(
1477 ap[0].len = 0;1526 ap[0].len = 0;
1478 ap[1].len = 0;1527 ap[1].len = 0;
14791528
1480 try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc);1529 try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]);
14811530
1482 var i: usize = 0;1531 var i: usize = 0;
1483 while (i < nq) : (i += 1) {1532 while (i < nq) : (i += 1) {
...@@ -1492,248 +1541,257 @@ fn linuxLookupNameFromDns(...@@ -1492,248 +1541,257 @@ fn linuxLookupNameFromDns(
1492}1541}
14931542
1494const ResolvConf = struct {1543const ResolvConf = struct {
1544 gpa: Allocator,
1495 attempts: u32,1545 attempts: u32,
1496 ndots: u32,1546 ndots: u32,
1497 timeout: u32,1547 timeout: u32,
1498 search: std.ArrayList(u8),1548 search: ArrayList(u8),
1499 ns: std.ArrayList(LookupAddr),1549 /// TODO there are actually only allowed to be maximum 3 nameservers, no need
1550 /// for an array list.
1551 ns: ArrayList(LookupAddr),
1552
1553 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1554 /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1555 fn init(rc: *ResolvConf, gpa: Allocator) !void {
1556 rc.* = .{
1557 .gpa = gpa,
1558 .ns = .empty,
1559 .search = .empty,
1560 .ndots = 1,
1561 .timeout = 5,
1562 .attempts = 2,
1563 };
1564 errdefer rc.deinit();
1565
1566 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1567 error.FileNotFound,
1568 error.NotDir,
1569 error.AccessDenied,
1570 => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53),
1571 else => |e| return e,
1572 };
1573 defer file.close();
15001574
1501 fn deinit(rc: *ResolvConf) void {1575 var line_buf: [512]u8 = undefined;
1502 rc.ns.deinit();1576 var file_reader = file.reader(&line_buf);
1503 rc.search.deinit();1577 return parse(rc, &file_reader.interface) catch |err| switch (err) {
1504 rc.* = undefined;1578 error.ReadFailed => return file_reader.err.?,
1579 else => |e| return e,
1580 };
1505 }1581 }
1506};
1507
1508/// Ignores lines longer than 512 bytes.
1509/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1510fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void {
1511 rc.* = ResolvConf{
1512 .ns = std.ArrayList(LookupAddr).init(allocator),
1513 .search = std.ArrayList(u8).init(allocator),
1514 .ndots = 1,
1515 .timeout = 5,
1516 .attempts = 2,
1517 };
1518 errdefer rc.deinit();
15191582
1520 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {1583 const Directive = enum { options, nameserver, domain, search };
1521 error.FileNotFound,1584 const Option = enum { ndots, attempts, timeout };
1522 error.NotDir,
1523 error.AccessDenied,
1524 => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53),
1525 else => |e| return e,
1526 };
1527 defer file.close();
15281585
1529 var buf_reader = std.io.bufferedReader(file.deprecatedReader());1586 fn parse(rc: *ResolvConf, reader: *io.Reader) !void {
1530 const stream = buf_reader.reader();1587 const gpa = rc.gpa;
1531 var line_buf: [512]u8 = undefined;1588 while (reader.takeSentinel('\n')) |line_with_comment| {
1532 while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {1589 const line = line: {
1533 error.StreamTooLong => blk: {1590 var split = mem.splitScalar(u8, line_with_comment, '#');
1534 // Skip to the delimiter in the stream, to fix parsing1591 break :line split.first();
1535 try stream.skipUntilDelimiterOrEof('\n');1592 };
1536 // Give an empty line to the while loop, which will be skipped.1593 var line_it = mem.tokenizeAny(u8, line, " \t");
1537 break :blk line_buf[0..0];1594
1538 },1595 const token = line_it.next() orelse continue;
1539 else => |e| return e,1596 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
1540 }) |line| {1597 .options => while (line_it.next()) |sub_tok| {
1541 const no_comment_line = no_comment_line: {1598 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1542 var split = mem.splitScalar(u8, line, '#');1599 const name = colon_it.first();
1543 break :no_comment_line split.first();1600 const value_txt = colon_it.next() orelse continue;
1544 };1601 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1545 var line_it = mem.tokenizeAny(u8, no_comment_line, " \t");1602 error.Overflow => 255,
15461603 error.InvalidCharacter => continue,
1547 const token = line_it.next() orelse continue;1604 };
1548 if (mem.eql(u8, token, "options")) {1605 switch (std.meta.stringToEnum(Option, name) orelse continue) {
1549 while (line_it.next()) |sub_tok| {1606 .ndots => rc.ndots = @min(value, 15),
1550 var colon_it = mem.splitScalar(u8, sub_tok, ':');1607 .attempts => rc.attempts = @min(value, 10),
1551 const name = colon_it.first();1608 .timeout => rc.timeout = @min(value, 60),
1552 const value_txt = colon_it.next() orelse continue;1609 }
1553 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {1610 },
1554 // TODO https://github.com/ziglang/zig/issues/118121611 .nameserver => {
1555 error.Overflow => @as(u8, 255),1612 const ip_txt = line_it.next() orelse continue;
1556 error.InvalidCharacter => continue,1613 try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53);
1557 };1614 },
1558 if (mem.eql(u8, name, "ndots")) {1615 .domain, .search => {
1559 rc.ndots = @min(value, 15);1616 rc.search.items.len = 0;
1560 } else if (mem.eql(u8, name, "attempts")) {1617 try rc.search.appendSlice(gpa, line_it.rest());
1561 rc.attempts = @min(value, 10);1618 },
1562 } else if (mem.eql(u8, name, "timeout")) {
1563 rc.timeout = @min(value, 60);
1564 }
1565 }1619 }
1566 } else if (mem.eql(u8, token, "nameserver")) {1620 } else |err| switch (err) {
1567 const ip_txt = line_it.next() orelse continue;1621 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
1568 try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53);1622 else => |e| return e,
1569 } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) {
1570 rc.search.items.len = 0;
1571 try rc.search.appendSlice(line_it.rest());
1572 }1623 }
1573 }
15741624
1575 if (rc.ns.items.len == 0) {1625 if (rc.ns.items.len == 0) {
1576 return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53);1626 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1627 }
1577 }1628 }
1578}
15791629
1580fn linuxLookupNameFromNumericUnspec(1630 fn resMSendRc(
1581 addrs: *std.ArrayList(LookupAddr),1631 rc: ResolvConf,
1582 name: []const u8,1632 queries: []const []const u8,
1583 port: u16,1633 answers: [][]u8,
1584) !void {1634 answer_bufs: []const []u8,
1585 const addr = try Address.resolveIp(name, port);1635 ) !void {
1586 (try addrs.addOne()).* = LookupAddr{ .addr = addr };1636 const gpa = rc.gpa;
1587}1637 const timeout = 1000 * rc.timeout;
1638 const attempts = rc.attempts;
15881639
1589fn resMSendRc(1640 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1590 queries: []const []const u8,1641 var family: posix.sa_family_t = posix.AF.INET;
1591 answers: [][]u8,
1592 answer_bufs: []const []u8,
1593 rc: ResolvConf,
1594) !void {
1595 const timeout = 1000 * rc.timeout;
1596 const attempts = rc.attempts;
15971642
1598 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);1643 var ns_list: ArrayList(Address) = .empty;
1599 var family: posix.sa_family_t = posix.AF.INET;1644 defer ns_list.deinit(gpa);
16001645
1601 var ns_list = std.ArrayList(Address).init(rc.ns.allocator);1646 try ns_list.resize(gpa, rc.ns.items.len);
1602 defer ns_list.deinit();
16031647
1604 try ns_list.resize(rc.ns.items.len);1648 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1605 const ns = ns_list.items;1649 ns.* = iplit.addr;
16061650 assert(ns.getPort() == 53);
1607 for (rc.ns.items, 0..) |iplit, i| {1651 if (iplit.addr.any.family != posix.AF.INET) {
1608 ns[i] = iplit.addr;1652 family = posix.AF.INET6;
1609 assert(ns[i].getPort() == 53);1653 }
1610 if (iplit.addr.any.family != posix.AF.INET) {
1611 family = posix.AF.INET6;
1612 }1654 }
1613 }
16141655
1615 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;1656 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1616 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {1657 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1617 error.AddressFamilyNotSupported => blk: {1658 error.AddressFamilyNotSupported => blk: {
1618 // Handle case where system lacks IPv6 support1659 // Handle case where system lacks IPv6 support
1619 if (family == posix.AF.INET6) {1660 if (family == posix.AF.INET6) {
1620 family = posix.AF.INET;1661 family = posix.AF.INET;
1621 break :blk try posix.socket(posix.AF.INET, flags, 0);1662 break :blk try posix.socket(posix.AF.INET, flags, 0);
1663 }
1664 return err;
1665 },
1666 else => |e| return e,
1667 };
1668 defer Stream.close(.{ .handle = fd });
1669
1670 // Past this point, there are no errors. Each individual query will
1671 // yield either no reply (indicated by zero length) or an answer
1672 // packet which is up to the caller to interpret.
1673
1674 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1675 if (family == posix.AF.INET6) {
1676 try posix.setsockopt(
1677 fd,
1678 posix.SOL.IPV6,
1679 std.os.linux.IPV6.V6ONLY,
1680 &mem.toBytes(@as(c_int, 0)),
1681 );
1682 for (ns_list.items) |*ns| {
1683 if (ns.any.family != posix.AF.INET) continue;
1684 mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian);
1685 ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1686 ns.any.family = posix.AF.INET6;
1687 ns.in6.sa.flowinfo = 0;
1688 ns.in6.sa.scope_id = 0;
1622 }1689 }
1623 return err;1690 sl = @sizeOf(posix.sockaddr.in6);
1624 },
1625 else => |e| return e,
1626 };
1627 defer Stream.close(.{ .handle = fd });
1628
1629 // Past this point, there are no errors. Each individual query will
1630 // yield either no reply (indicated by zero length) or an answer
1631 // packet which is up to the caller to interpret.
1632
1633 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1634 if (family == posix.AF.INET6) {
1635 try posix.setsockopt(
1636 fd,
1637 posix.SOL.IPV6,
1638 std.os.linux.IPV6.V6ONLY,
1639 &mem.toBytes(@as(c_int, 0)),
1640 );
1641 for (0..ns.len) |i| {
1642 if (ns[i].any.family != posix.AF.INET) continue;
1643 mem.writeInt(u32, ns[i].in6.sa.addr[12..], ns[i].in.sa.addr, native_endian);
1644 ns[i].in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1645 ns[i].any.family = posix.AF.INET6;
1646 ns[i].in6.sa.flowinfo = 0;
1647 ns[i].in6.sa.scope_id = 0;
1648 }1691 }
1649 sl = @sizeOf(posix.sockaddr.in6);1692
1650 }1693 // Get local address and open/bind a socket
16511694 var sa: Address = undefined;
1652 // Get local address and open/bind a socket1695 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1653 var sa: Address = undefined;1696 sa.any.family = family;
1654 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);1697 try posix.bind(fd, &sa.any, sl);
1655 sa.any.family = family;1698
1656 try posix.bind(fd, &sa.any, sl);1699 var pfd = [1]posix.pollfd{posix.pollfd{
16571700 .fd = fd,
1658 var pfd = [1]posix.pollfd{posix.pollfd{1701 .events = posix.POLL.IN,
1659 .fd = fd,1702 .revents = undefined,
1660 .events = posix.POLL.IN,1703 }};
1661 .revents = undefined,1704 const retry_interval = timeout / attempts;
1662 }};1705 var next: u32 = 0;
1663 const retry_interval = timeout / attempts;1706 var t2: u64 = @bitCast(std.time.milliTimestamp());
1664 var next: u32 = 0;1707 const t0 = t2;
1665 var t2: u64 = @bitCast(std.time.milliTimestamp());1708 var t1 = t2 - retry_interval;
1666 const t0 = t2;1709
1667 var t1 = t2 - retry_interval;1710 var servfail_retry: usize = undefined;
16681711
1669 var servfail_retry: usize = undefined;1712 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
16701713 if (t2 - t1 >= retry_interval) {
1671 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {1714 // Query all configured nameservers in parallel
1672 if (t2 - t1 >= retry_interval) {1715 var i: usize = 0;
1673 // Query all configured nameservers in parallel1716 while (i < queries.len) : (i += 1) {
1674 var i: usize = 0;1717 if (answers[i].len == 0) {
1675 while (i < queries.len) : (i += 1) {1718 for (ns_list.items) |*ns| {
1676 if (answers[i].len == 0) {1719 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1677 var j: usize = 0;1720 }
1678 while (j < ns.len) : (j += 1) {
1679 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1680 }1721 }
1681 }1722 }
1723 t1 = t2;
1724 servfail_retry = 2 * queries.len;
1682 }1725 }
1683 t1 = t2;
1684 servfail_retry = 2 * queries.len;
1685 }
16861726
1687 // Wait for a response, or until time to retry1727 // Wait for a response, or until time to retry
1688 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);1728 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1689 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;1729 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1690 if (nevents == 0) continue;1730 if (nevents == 0) continue;
1731
1732 while (true) {
1733 var sl_copy = sl;
1734 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1735
1736 // Ignore non-identifiable packets
1737 if (rlen < 4) continue;
1738
1739 // Ignore replies from addresses we didn't send to
1740 const ns = for (ns_list.items) |*ns| {
1741 if (ns.eql(sa)) break ns;
1742 } else continue;
1743
1744 // Find which query this answer goes with, if any
1745 var i: usize = next;
1746 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1747 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1748 {}
1749
1750 if (i == queries.len) continue;
1751 if (answers[i].len != 0) continue;
1752
1753 // Only accept positive or negative responses;
1754 // retry immediately on server failure, and ignore
1755 // all other codes such as refusal.
1756 switch (answer_bufs[next][3] & 15) {
1757 0, 3 => {},
1758 2 => if (servfail_retry != 0) {
1759 servfail_retry -= 1;
1760 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1761 },
1762 else => continue,
1763 }
16911764
1692 while (true) {1765 // Store answer in the right slot, or update next
1693 var sl_copy = sl;1766 // available temp slot if it's already in place.
1694 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;1767 answers[i].len = rlen;
16951768 if (i == next) {
1696 // Ignore non-identifiable packets1769 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1697 if (rlen < 4) continue;1770 } else {
16981771 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1699 // Ignore replies from addresses we didn't send to1772 }
1700 var j: usize = 0;
1701 while (j < ns.len and !ns[j].eql(sa)) : (j += 1) {}
1702 if (j == ns.len) continue;
1703
1704 // Find which query this answer goes with, if any
1705 var i: usize = next;
1706 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1707 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1708 {}
1709
1710 if (i == queries.len) continue;
1711 if (answers[i].len != 0) continue;
1712
1713 // Only accept positive or negative responses;
1714 // retry immediately on server failure, and ignore
1715 // all other codes such as refusal.
1716 switch (answer_bufs[next][3] & 15) {
1717 0, 3 => {},
1718 2 => if (servfail_retry != 0) {
1719 servfail_retry -= 1;
1720 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns[j].any, sl) catch undefined;
1721 },
1722 else => continue,
1723 }
17241773
1725 // Store answer in the right slot, or update next1774 if (next == queries.len) break :outer;
1726 // available temp slot if it's already in place.
1727 answers[i].len = rlen;
1728 if (i == next) {
1729 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1730 } else {
1731 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1732 }1775 }
1733
1734 if (next == queries.len) break :outer;
1735 }1776 }
1736 }1777 }
1778
1779 fn deinit(rc: *ResolvConf) void {
1780 const gpa = rc.gpa;
1781 rc.ns.deinit(gpa);
1782 rc.search.deinit(gpa);
1783 rc.* = undefined;
1784 }
1785};
1786
1787fn linuxLookupNameFromNumericUnspec(
1788 gpa: Allocator,
1789 addrs: *ArrayList(LookupAddr),
1790 name: []const u8,
1791 port: u16,
1792) !void {
1793 const addr = try Address.resolveIp(name, port);
1794 try addrs.append(gpa, .{ .addr = addr });
1737}1795}
17381796
1739fn dnsParse(1797fn dnsParse(
...@@ -1770,20 +1828,19 @@ fn dnsParse(...@@ -1770,20 +1828,19 @@ fn dnsParse(
1770}1828}
17711829
1772fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {1830fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1831 const gpa = ctx.gpa;
1773 switch (rr) {1832 switch (rr) {
1774 posix.RR.A => {1833 posix.RR.A => {
1775 if (data.len != 4) return error.InvalidDnsARecord;1834 if (data.len != 4) return error.InvalidDnsARecord;
1776 const new_addr = try ctx.addrs.addOne();1835 try ctx.addrs.append(gpa, .{
1777 new_addr.* = LookupAddr{
1778 .addr = Address.initIp4(data[0..4].*, ctx.port),1836 .addr = Address.initIp4(data[0..4].*, ctx.port),
1779 };1837 });
1780 },1838 },
1781 posix.RR.AAAA => {1839 posix.RR.AAAA => {
1782 if (data.len != 16) return error.InvalidDnsAAAARecord;1840 if (data.len != 16) return error.InvalidDnsAAAARecord;
1783 const new_addr = try ctx.addrs.addOne();1841 try ctx.addrs.append(gpa, .{
1784 new_addr.* = LookupAddr{
1785 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),1842 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1786 };1843 });
1787 },1844 },
1788 posix.RR.CNAME => {1845 posix.RR.CNAME => {
1789 var tmp: [256]u8 = undefined;1846 var tmp: [256]u8 = undefined;
...@@ -1792,7 +1849,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1792,7 +1849,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1792 const canon_name = mem.sliceTo(&tmp, 0);1849 const canon_name = mem.sliceTo(&tmp, 0);
1793 if (isValidHostName(canon_name)) {1850 if (isValidHostName(canon_name)) {
1794 ctx.canon.items.len = 0;1851 ctx.canon.items.len = 0;
1795 try ctx.canon.appendSlice(canon_name);1852 try ctx.canon.appendSlice(gpa, canon_name);
1796 }1853 }
1797 },1854 },
1798 else => return,1855 else => return,
...@@ -1802,7 +1859,12 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1802,7 +1859,12 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1802pub const Stream = struct {1859pub const Stream = struct {
1803 /// Underlying platform-defined type which may or may not be1860 /// Underlying platform-defined type which may or may not be
1804 /// interchangeable with a file system file descriptor.1861 /// interchangeable with a file system file descriptor.
1805 handle: posix.socket_t,1862 handle: Handle,
1863
1864 pub const Handle = switch (native_os) {
1865 .windows => windows.ws2_32.SOCKET,
1866 else => posix.fd_t,
1867 };
18061868
1807 pub fn close(s: Stream) void {1869 pub fn close(s: Stream) void {
1808 switch (native_os) {1870 switch (native_os) {
...@@ -1811,20 +1873,342 @@ pub const Stream = struct {...@@ -1811,20 +1873,342 @@ pub const Stream = struct {
1811 }1873 }
1812 }1874 }
18131875
1814 pub const ReadError = posix.ReadError;1876 pub const ReadError = posix.ReadError || error{
1815 pub const WriteError = posix.WriteError;1877 SocketNotBound,
1878 MessageTooBig,
1879 NetworkSubsystemFailed,
1880 ConnectionResetByPeer,
1881 SocketNotConnected,
1882 };
1883
1884 pub const WriteError = posix.SendMsgError || error{
1885 ConnectionResetByPeer,
1886 SocketNotBound,
1887 MessageTooBig,
1888 NetworkSubsystemFailed,
1889 SystemResources,
1890 SocketNotConnected,
1891 Unexpected,
1892 };
1893
1894 pub const Reader = switch (native_os) {
1895 .windows => struct {
1896 /// Use `interface` for portable code.
1897 interface_state: io.Reader,
1898 /// Use `getStream` for portable code.
1899 net_stream: Stream,
1900 /// Use `getError` for portable code.
1901 error_state: ?Error,
1902
1903 pub const Error = ReadError;
1904
1905 pub fn getStream(r: *const Reader) Stream {
1906 return r.stream;
1907 }
1908
1909 pub fn getError(r: *const Reader) ?Error {
1910 return r.error_state;
1911 }
1912
1913 pub fn interface(r: *Reader) *io.Reader {
1914 return &r.interface_state;
1915 }
1916
1917 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1918 return .{
1919 .interface_state = .{
1920 .vtable = &.{ .stream = stream },
1921 .buffer = buffer,
1922 .seek = 0,
1923 .end = 0,
1924 },
1925 .net_stream = net_stream,
1926 .error_state = null,
1927 };
1928 }
1929
1930 fn stream(io_r: *io.Reader, io_w: *io.Writer, limit: io.Limit) io.Reader.StreamError!usize {
1931 const r: *Reader = @alignCast(@fieldParentPtr("interface_state", io_r));
1932 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
1933 const bufs = try io_w.writableVectorWsa(&iovecs, limit);
1934 assert(bufs[0].len != 0);
1935 const n = streamBufs(r, bufs) catch |err| {
1936 r.error_state = err;
1937 return error.ReadFailed;
1938 };
1939 if (n == 0) return error.EndOfStream;
1940 return n;
1941 }
1942
1943 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
1944 var n: u32 = undefined;
1945 var flags: u32 = 0;
1946 const rc = windows.ws2_32.WSARecvFrom(r.net_stream.handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null, null, null);
1947 if (rc != 0) switch (windows.ws2_32.WSAGetLastError()) {
1948 .WSAECONNRESET => return error.ConnectionResetByPeer,
1949 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
1950 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
1951 .WSAEINVAL => return error.SocketNotBound,
1952 .WSAEMSGSIZE => return error.MessageTooBig,
1953 .WSAENETDOWN => return error.NetworkSubsystemFailed,
1954 .WSAENETRESET => return error.ConnectionResetByPeer,
1955 .WSAENOTCONN => return error.SocketNotConnected,
1956 .WSAEWOULDBLOCK => return error.WouldBlock,
1957 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
1958 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
1959 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
1960 else => |err| return windows.unexpectedWSAError(err),
1961 };
1962 return n;
1963 }
1964 },
1965 else => struct {
1966 /// Use `getStream`, `interface`, and `getError` for portable code.
1967 file_reader: File.Reader,
1968
1969 pub const Error = ReadError;
1970
1971 pub fn interface(r: *Reader) *io.Reader {
1972 return &r.file_reader.interface;
1973 }
1974
1975 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1976 return .{
1977 .file_reader = .{
1978 .interface = File.Reader.initInterface(buffer),
1979 .file = .{ .handle = net_stream.handle },
1980 .mode = .streaming,
1981 .seek_err = error.Unseekable,
1982 },
1983 };
1984 }
1985
1986 pub fn getStream(r: *const Reader) Stream {
1987 return .{ .handle = r.file_reader.file.handle };
1988 }
1989
1990 pub fn getError(r: *const Reader) ?Error {
1991 return r.file_reader.err;
1992 }
1993 },
1994 };
1995
1996 pub const Writer = switch (native_os) {
1997 .windows => struct {
1998 /// This field is present on all systems.
1999 interface: io.Writer,
2000 /// Use `getStream` for cross-platform support.
2001 stream: Stream,
2002 /// This field is present on all systems.
2003 err: ?Error = null,
2004
2005 pub const Error = WriteError;
2006
2007 pub fn init(stream: Stream, buffer: []u8) Writer {
2008 return .{
2009 .stream = stream,
2010 .interface = .{
2011 .vtable = &.{ .drain = drain },
2012 .buffer = buffer,
2013 },
2014 };
2015 }
2016
2017 pub fn getStream(w: *const Writer) Stream {
2018 return w.stream;
2019 }
18162020
1817 pub const Reader = io.GenericReader(Stream, ReadError, read);2021 fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
1818 pub const Writer = io.GenericWriter(Stream, WriteError, write);2022 const cap = std.math.maxInt(u32);
2023 var remaining = bytes;
2024 while (remaining.len > cap) {
2025 if (v.len - i.* == 0) return;
2026 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap };
2027 i.* += 1;
2028 remaining = remaining[cap..];
2029 } else {
2030 @branchHint(.likely);
2031 if (v.len - i.* == 0) return;
2032 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) };
2033 i.* += 1;
2034 }
2035 }
18192036
1820 pub fn reader(self: Stream) Reader {2037 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
1821 return .{ .context = self };2038 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2039 const buffered = io_w.buffered();
2040 comptime assert(native_os == .windows);
2041 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
2042 var len: u32 = 0;
2043 addWsaBuf(&iovecs, &len, buffered);
2044 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes);
2045 const pattern = data[data.len - 1];
2046 if (iovecs.len - len != 0) switch (splat) {
2047 0 => {},
2048 1 => addWsaBuf(&iovecs, &len, pattern),
2049 else => switch (pattern.len) {
2050 0 => {},
2051 1 => {
2052 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2053 var backup_buffer: [64]u8 = undefined;
2054 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2055 splat_buffer_candidate
2056 else
2057 &backup_buffer;
2058 const memset_len = @min(splat_buffer.len, splat);
2059 const buf = splat_buffer[0..memset_len];
2060 @memset(buf, pattern[0]);
2061 addWsaBuf(&iovecs, &len, buf);
2062 var remaining_splat = splat - buf.len;
2063 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2064 addWsaBuf(&iovecs, &len, splat_buffer);
2065 remaining_splat -= splat_buffer.len;
2066 }
2067 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
2068 },
2069 else => for (0..@min(splat, iovecs.len - len)) |_| {
2070 addWsaBuf(&iovecs, &len, pattern);
2071 },
2072 },
2073 };
2074 const n = sendBufs(w.stream.handle, iovecs[0..len]) catch |err| {
2075 w.err = err;
2076 return error.WriteFailed;
2077 };
2078 return io_w.consume(n);
2079 }
2080
2081 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2082 var n: u32 = undefined;
2083 const rc = windows.ws2_32.WSASend(handle, bufs.ptr, @intCast(bufs.len), &n, 0, null, null);
2084 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2085 .WSAECONNABORTED => return error.ConnectionResetByPeer,
2086 .WSAECONNRESET => return error.ConnectionResetByPeer,
2087 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2088 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2089 .WSAEINVAL => return error.SocketNotBound,
2090 .WSAEMSGSIZE => return error.MessageTooBig,
2091 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2092 .WSAENETRESET => return error.ConnectionResetByPeer,
2093 .WSAENOBUFS => return error.SystemResources,
2094 .WSAENOTCONN => return error.SocketNotConnected,
2095 .WSAENOTSOCK => unreachable, // not a socket
2096 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
2097 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
2098 .WSAEWOULDBLOCK => return error.WouldBlock,
2099 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2100 .WSA_IO_PENDING => unreachable, // not using overlapped I/O
2101 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2102 else => |err| return windows.unexpectedWSAError(err),
2103 };
2104 return n;
2105 }
2106 },
2107 else => struct {
2108 /// This field is present on all systems.
2109 interface: io.Writer,
2110
2111 err: ?Error = null,
2112 file_writer: File.Writer,
2113
2114 pub const Error = WriteError;
2115
2116 pub fn init(stream: Stream, buffer: []u8) Writer {
2117 return .{
2118 .interface = .{
2119 .vtable = &.{
2120 .drain = drain,
2121 .sendFile = sendFile,
2122 },
2123 .buffer = buffer,
2124 },
2125 .file_writer = .initMode(.{ .handle = stream.handle }, &.{}, .streaming),
2126 };
2127 }
2128
2129 pub fn getStream(w: *const Writer) Stream {
2130 return .{ .handle = w.file_writer.file.handle };
2131 }
2132
2133 fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
2134 // OS checks ptr addr before length so zero length vectors must be omitted.
2135 if (bytes.len == 0) return;
2136 if (v.len - i.* == 0) return;
2137 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
2138 i.* += 1;
2139 }
2140
2141 fn drain(io_w: *io.Writer, data: []const []const u8, splat: usize) io.Writer.Error!usize {
2142 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2143 const buffered = io_w.buffered();
2144 var iovecs: [max_buffers_len]posix.iovec_const = undefined;
2145 var msg: posix.msghdr_const = .{
2146 .name = null,
2147 .namelen = 0,
2148 .iov = &iovecs,
2149 .iovlen = 0,
2150 .control = null,
2151 .controllen = 0,
2152 .flags = 0,
2153 };
2154 addBuf(&iovecs, &msg.iovlen, buffered);
2155 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
2156 const pattern = data[data.len - 1];
2157 if (iovecs.len - msg.iovlen != 0) switch (splat) {
2158 0 => {},
2159 1 => addBuf(&iovecs, &msg.iovlen, pattern),
2160 else => switch (pattern.len) {
2161 0 => {},
2162 1 => {
2163 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2164 var backup_buffer: [64]u8 = undefined;
2165 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2166 splat_buffer_candidate
2167 else
2168 &backup_buffer;
2169 const memset_len = @min(splat_buffer.len, splat);
2170 const buf = splat_buffer[0..memset_len];
2171 @memset(buf, pattern[0]);
2172 addBuf(&iovecs, &msg.iovlen, buf);
2173 var remaining_splat = splat - buf.len;
2174 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2175 assert(buf.len == splat_buffer.len);
2176 addBuf(&iovecs, &msg.iovlen, splat_buffer);
2177 remaining_splat -= splat_buffer.len;
2178 }
2179 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
2180 },
2181 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
2182 addBuf(&iovecs, &msg.iovlen, pattern);
2183 },
2184 },
2185 };
2186 const flags = posix.MSG.NOSIGNAL;
2187 return io_w.consume(posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
2188 w.err = err;
2189 return error.WriteFailed;
2190 });
2191 }
2192
2193 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {
2194 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2195 const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit);
2196 return io_w.consume(n);
2197 }
2198 },
2199 };
2200
2201 pub fn reader(stream: Stream, buffer: []u8) Reader {
2202 return .init(stream, buffer);
1822 }2203 }
18232204
1824 pub fn writer(self: Stream) Writer {2205 pub fn writer(stream: Stream, buffer: []u8) Writer {
1825 return .{ .context = self };2206 return .init(stream, buffer);
1826 }2207 }
18272208
2209 const max_buffers_len = 8;
2210
2211 /// Deprecated in favor of `Reader`.
1828 pub fn read(self: Stream, buffer: []u8) ReadError!usize {2212 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
1829 if (native_os == .windows) {2213 if (native_os == .windows) {
1830 return windows.ReadFile(self.handle, buffer, null);2214 return windows.ReadFile(self.handle, buffer, null);
...@@ -1833,10 +2217,10 @@ pub const Stream = struct {...@@ -1833,10 +2217,10 @@ pub const Stream = struct {
1833 return posix.read(self.handle, buffer);2217 return posix.read(self.handle, buffer);
1834 }2218 }
18352219
2220 /// Deprecated in favor of `Reader`.
1836 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {2221 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
1837 if (native_os == .windows) {2222 if (native_os == .windows) {
1838 // TODO improve this to use ReadFileScatter2223 if (iovecs.len == 0) return 0;
1839 if (iovecs.len == 0) return @as(usize, 0);
1840 const first = iovecs[0];2224 const first = iovecs[0];
1841 return windows.ReadFile(s.handle, first.base[0..first.len], null);2225 return windows.ReadFile(s.handle, first.base[0..first.len], null);
1842 }2226 }
...@@ -1844,18 +2228,7 @@ pub const Stream = struct {...@@ -1844,18 +2228,7 @@ pub const Stream = struct {
1844 return posix.readv(s.handle, iovecs);2228 return posix.readv(s.handle, iovecs);
1845 }2229 }
18462230
1847 /// Returns the number of bytes read. If the number read is smaller than2231 /// Deprecated in favor of `Reader`.
1848 /// `buffer.len`, it means the stream reached the end. Reaching the end of
1849 /// a stream is not an error condition.
1850 pub fn readAll(s: Stream, buffer: []u8) ReadError!usize {
1851 return readAtLeast(s, buffer, buffer.len);
1852 }
1853
1854 /// Returns the number of bytes read, calling the underlying read function
1855 /// the minimal number of times until the buffer has at least `len` bytes
1856 /// filled. If the number read is less than `len` it means the stream
1857 /// reached the end. Reaching the end of the stream is not an error
1858 /// condition.
1859 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {2232 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
1860 assert(len <= buffer.len);2233 assert(len <= buffer.len);
1861 var index: usize = 0;2234 var index: usize = 0;
...@@ -1867,17 +2240,13 @@ pub const Stream = struct {...@@ -1867,17 +2240,13 @@ pub const Stream = struct {
1867 return index;2240 return index;
1868 }2241 }
18692242
1870 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's2243 /// Deprecated in favor of `Writer`.
1871 /// file system thread instead of non-blocking. It needs to be reworked to properly
1872 /// use non-blocking I/O.
1873 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {2244 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
1874 if (native_os == .windows) {2245 var stream_writer = self.writer(&.{});
1875 return windows.WriteFile(self.handle, buffer, null);2246 return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?;
1876 }
1877
1878 return posix.write(self.handle, buffer);
1879 }2247 }
18802248
2249 /// Deprecated in favor of `Writer`.
1881 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {2250 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
1882 var index: usize = 0;2251 var index: usize = 0;
1883 while (index < bytes.len) {2252 while (index < bytes.len) {
...@@ -1885,16 +2254,12 @@ pub const Stream = struct {...@@ -1885,16 +2254,12 @@ pub const Stream = struct {
1885 }2254 }
1886 }2255 }
18872256
1888 /// See https://github.com/ziglang/zig/issues/76992257 /// Deprecated in favor of `Writer`.
1889 /// See equivalent function: `std.fs.File.writev`.
1890 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {2258 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
1891 return posix.writev(self.handle, iovecs);2259 return @errorCast(posix.writev(self.handle, iovecs));
1892 }2260 }
18932261
1894 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in2262 /// Deprecated in favor of `Writer`.
1895 /// order to handle partial writes from the underlying OS layer.
1896 /// See https://github.com/ziglang/zig/issues/7699
1897 /// See equivalent function: `std.fs.File.writevAll`.
1898 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {2263 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
1899 if (iovecs.len == 0) return;2264 if (iovecs.len == 0) return;
19002265
...@@ -1914,10 +2279,10 @@ pub const Stream = struct {...@@ -1914,10 +2279,10 @@ pub const Stream = struct {
19142279
1915pub const Server = struct {2280pub const Server = struct {
1916 listen_address: Address,2281 listen_address: Address,
1917 stream: std.net.Stream,2282 stream: Stream,
19182283
1919 pub const Connection = struct {2284 pub const Connection = struct {
1920 stream: std.net.Stream,2285 stream: Stream,
1921 address: Address,2286 address: Address,
1922 };2287 };
19232288
lib/std/net/test.zig+8-4
...@@ -208,7 +208,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -208,7 +208,8 @@ test "listen on a port, send bytes, receive bytes" {
208 const socket = try net.tcpConnectToAddress(server_address);208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();209 defer socket.close();
210210
211 _ = try socket.writer().writeAll("Hello world!");211 var stream_writer = socket.writer(&.{});
212 try stream_writer.interface.writeAll("Hello world!");
212 }213 }
213 };214 };
214215
...@@ -218,7 +219,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -218,7 +219,8 @@ test "listen on a port, send bytes, receive bytes" {
218 var client = try server.accept();219 var client = try server.accept();
219 defer client.stream.close();220 defer client.stream.close();
220 var buf: [16]u8 = undefined;221 var buf: [16]u8 = undefined;
221 const n = try client.stream.reader().read(&buf);222 var stream_reader = client.stream.reader(&.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);
222224
223 try testing.expectEqual(@as(usize, 12), n);225 try testing.expectEqual(@as(usize, 12), n);
224 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
...@@ -299,7 +301,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -299,7 +301,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
299 const socket = try net.connectUnixSocket(path);301 const socket = try net.connectUnixSocket(path);
300 defer socket.close();302 defer socket.close();
301303
302 _ = try socket.writer().writeAll("Hello world!");304 var stream_writer = socket.writer(&.{});
305 try stream_writer.interface.writeAll("Hello world!");
303 }306 }
304 };307 };
305308
...@@ -309,7 +312,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -309,7 +312,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
309 var client = try server.accept();312 var client = try server.accept();
310 defer client.stream.close();313 defer client.stream.close();
311 var buf: [16]u8 = undefined;314 var buf: [16]u8 = undefined;
312 const n = try client.stream.reader().read(&buf);315 var stream_reader = client.stream.reader(&.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);
313317
314 try testing.expectEqual(@as(usize, 12), n);318 try testing.expectEqual(@as(usize, 12), n);
315 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
src/IncrementalDebugServer.zig+4-3
...@@ -55,14 +55,15 @@ fn runThread(ids: *IncrementalDebugServer) void {...@@ -55,14 +55,15 @@ fn runThread(ids: *IncrementalDebugServer) void {
55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
56 defer conn.stream.close();56 defer conn.stream.close();
5757
58 var stream_reader = conn.stream.reader(&cmd_buf);
59
58 while (ids.running.load(.monotonic)) {60 while (ids.running.load(.monotonic)) {
59 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");61 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
60 var fbs = std.io.fixedBufferStream(&cmd_buf);62 const untrimmed = stream_reader.interface().takeSentinel('\n') catch |err| switch (err) {
61 conn.stream.reader().streamUntilDelimiter(fbs.writer(), '\n', cmd_buf.len) catch |err| switch (err) {
62 error.EndOfStream => break,63 error.EndOfStream => break,
63 else => @panic("IncrementalDebugServer: failed to read command"),64 else => @panic("IncrementalDebugServer: failed to read command"),
64 };65 };
65 const cmd_and_arg = std.mem.trim(u8, fbs.getWritten(), " \t\r\n");66 const cmd_and_arg = std.mem.trim(u8, untrimmed, " \t\r\n");
66 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|67 const cmd: []const u8, const arg: []const u8 = if (std.mem.indexOfScalar(u8, cmd_and_arg, ' ')) |i|
67 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }68 .{ cmd_and_arg[0..i], cmd_and_arg[i + 1 ..] }
68 else69 else