authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-04 00:01:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
logbdf463bee2becd84b83bb9d66725420e03680df8
treec0a51d328eb42e28ec9315171c0f58d1f803bdc8
parent07cc4077fba5701ef582e17c795f9f5214915179

std.Io.net: partially implement HostName.lookup


5 files changed, 302 insertions(+), 56 deletions(-)

lib/std/Io.zig+8-3
...@@ -660,7 +660,7 @@ pub const VTable = struct {...@@ -660,7 +660,7 @@ pub const VTable = struct {
660660
661 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,661 listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server,
662 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,662 accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection,
663 netRead: *const fn (?*anyopaque, src: net.Stream, dest: *Io.Writer, limit: Io.Limit) net.Stream.Reader.Error!usize,663 netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize,
664 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,664 netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
665 netClose: *const fn (?*anyopaque, stream: net.Stream) void,665 netClose: *const fn (?*anyopaque, stream: net.Stream) void,
666};666};
...@@ -760,6 +760,11 @@ pub const File = struct {...@@ -760,6 +760,11 @@ pub const File = struct {
760 }760 }
761 return index;761 return index;
762 }762 }
763
764 pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError {
765 assert(std.fs.path.isAbsolute(absolute_path));
766 return Dir.cwd().openFile(io, absolute_path, flags);
767 }
763};768};
764769
765pub const Timestamp = enum(i96) {770pub const Timestamp = enum(i96) {
...@@ -1205,7 +1210,7 @@ pub fn asyncConcurrent(...@@ -1205,7 +1210,7 @@ pub fn asyncConcurrent(
1205 const Args = @TypeOf(args);1210 const Args = @TypeOf(args);
1206 const TypeErased = struct {1211 const TypeErased = struct {
1207 fn start(context: *const anyopaque, result: *anyopaque) void {1212 fn start(context: *const anyopaque, result: *anyopaque) void {
1208 const args_casted: *const Args = @alignCast(@ptrCast(context));1213 const args_casted: *const Args = @ptrCast(@alignCast(context));
1209 const result_casted: *Result = @ptrCast(@alignCast(result));1214 const result_casted: *Result = @ptrCast(@alignCast(result));
1210 result_casted.* = @call(.auto, function, args_casted.*);1215 result_casted.* = @call(.auto, function, args_casted.*);
1211 }1216 }
...@@ -1234,7 +1239,7 @@ pub fn asyncDetached(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf...@@ -1234,7 +1239,7 @@ pub fn asyncDetached(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf
1234 const Args = @TypeOf(args);1239 const Args = @TypeOf(args);
1235 const TypeErased = struct {1240 const TypeErased = struct {
1236 fn start(context: *const anyopaque) void {1241 fn start(context: *const anyopaque) void {
1237 const args_casted: *const Args = @alignCast(@ptrCast(context));1242 const args_casted: *const Args = @ptrCast(@alignCast(context));
1238 @call(.auto, function, args_casted.*);1243 @call(.auto, function, args_casted.*);
1239 }1244 }
1240 };1245 };
lib/std/Io/ThreadPool.zig+34-31
...@@ -233,7 +233,7 @@ fn async(...@@ -233,7 +233,7 @@ fn async(
233 start(context.ptr, result.ptr);233 start(context.ptr, result.ptr);
234 return null;234 return null;
235 }235 }
236 const pool: *Pool = @alignCast(@ptrCast(userdata));236 const pool: *Pool = @ptrCast(@alignCast(userdata));
237 const cpu_count = pool.cpu_count catch {237 const cpu_count = pool.cpu_count catch {
238 return asyncConcurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {238 return asyncConcurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
239 start(context.ptr, result.ptr);239 start(context.ptr, result.ptr);
...@@ -244,7 +244,7 @@ fn async(...@@ -244,7 +244,7 @@ fn async(
244 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));244 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
245 const result_offset = result_alignment.forward(context_offset + context.len);245 const result_offset = result_alignment.forward(context_offset + context.len);
246 const n = result_offset + result.len;246 const n = result_offset + result.len;
247 const closure: *AsyncClosure = @alignCast(@ptrCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {247 const closure: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
248 start(context.ptr, result.ptr);248 start(context.ptr, result.ptr);
249 return null;249 return null;
250 }));250 }));
...@@ -309,13 +309,13 @@ fn asyncConcurrent(...@@ -309,13 +309,13 @@ fn asyncConcurrent(
309) error{OutOfMemory}!*Io.AnyFuture {309) error{OutOfMemory}!*Io.AnyFuture {
310 if (builtin.single_threaded) unreachable;310 if (builtin.single_threaded) unreachable;
311311
312 const pool: *Pool = @alignCast(@ptrCast(userdata));312 const pool: *Pool = @ptrCast(@alignCast(userdata));
313 const cpu_count = pool.cpu_count catch 1;313 const cpu_count = pool.cpu_count catch 1;
314 const gpa = pool.allocator;314 const gpa = pool.allocator;
315 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));315 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
316 const result_offset = result_alignment.forward(context_offset + context.len);316 const result_offset = result_alignment.forward(context_offset + context.len);
317 const n = result_offset + result_len;317 const n = result_offset + result_len;
318 const closure: *AsyncClosure = @alignCast(@ptrCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));318 const closure: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));
319319
320 closure.* = .{320 closure.* = .{
321 .func = start,321 .func = start,
...@@ -399,11 +399,11 @@ fn asyncDetached(...@@ -399,11 +399,11 @@ fn asyncDetached(
399 start: *const fn (context: *const anyopaque) void,399 start: *const fn (context: *const anyopaque) void,
400) void {400) void {
401 if (builtin.single_threaded) return start(context.ptr);401 if (builtin.single_threaded) return start(context.ptr);
402 const pool: *Pool = @alignCast(@ptrCast(userdata));402 const pool: *Pool = @ptrCast(@alignCast(userdata));
403 const cpu_count = pool.cpu_count catch 1;403 const cpu_count = pool.cpu_count catch 1;
404 const gpa = pool.allocator;404 const gpa = pool.allocator;
405 const n = DetachedClosure.contextEnd(context_alignment, context.len);405 const n = DetachedClosure.contextEnd(context_alignment, context.len);
406 const closure: *DetachedClosure = @alignCast(@ptrCast(gpa.alignedAlloc(u8, .of(DetachedClosure), n) catch {406 const closure: *DetachedClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(DetachedClosure), n) catch {
407 return start(context.ptr);407 return start(context.ptr);
408 }));408 }));
409 closure.* = .{409 closure.* = .{
...@@ -451,7 +451,7 @@ fn await(...@@ -451,7 +451,7 @@ fn await(
451 result_alignment: std.mem.Alignment,451 result_alignment: std.mem.Alignment,
452) void {452) void {
453 _ = result_alignment;453 _ = result_alignment;
454 const pool: *Pool = @alignCast(@ptrCast(userdata));454 const pool: *Pool = @ptrCast(@alignCast(userdata));
455 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));455 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
456 closure.waitAndFree(pool.allocator, result);456 closure.waitAndFree(pool.allocator, result);
457}457}
...@@ -463,7 +463,7 @@ fn cancel(...@@ -463,7 +463,7 @@ fn cancel(
463 result_alignment: std.mem.Alignment,463 result_alignment: std.mem.Alignment,
464) void {464) void {
465 _ = result_alignment;465 _ = result_alignment;
466 const pool: *Pool = @alignCast(@ptrCast(userdata));466 const pool: *Pool = @ptrCast(@alignCast(userdata));
467 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));467 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
468 switch (@atomicRmw(468 switch (@atomicRmw(
469 std.Thread.Id,469 std.Thread.Id,
...@@ -486,7 +486,7 @@ fn cancel(...@@ -486,7 +486,7 @@ fn cancel(
486}486}
487487
488fn cancelRequested(userdata: ?*anyopaque) bool {488fn cancelRequested(userdata: ?*anyopaque) bool {
489 const pool: *Pool = @alignCast(@ptrCast(userdata));489 const pool: *Pool = @ptrCast(@alignCast(userdata));
490 _ = pool;490 _ = pool;
491 const closure = current_closure orelse return false;491 const closure = current_closure orelse return false;
492 return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == AsyncClosure.canceling_tid;492 return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == AsyncClosure.canceling_tid;
...@@ -520,7 +520,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut...@@ -520,7 +520,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
520}520}
521521
522fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {522fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
523 const pool: *Pool = @alignCast(@ptrCast(userdata));523 const pool: *Pool = @ptrCast(@alignCast(userdata));
524 comptime assert(@TypeOf(cond.state) == u64);524 comptime assert(@TypeOf(cond.state) == u64);
525 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);525 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
526 const cond_state = &ints[0];526 const cond_state = &ints[0];
...@@ -567,7 +567,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I...@@ -567,7 +567,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
567}567}
568568
569fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {569fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
570 const pool: *Pool = @alignCast(@ptrCast(userdata));570 const pool: *Pool = @ptrCast(@alignCast(userdata));
571 _ = pool;571 _ = pool;
572 comptime assert(@TypeOf(cond.state) == u64);572 comptime assert(@TypeOf(cond.state) == u64);
573 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);573 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
...@@ -624,7 +624,7 @@ fn createFile(...@@ -624,7 +624,7 @@ fn createFile(
624 sub_path: []const u8,624 sub_path: []const u8,
625 flags: Io.File.CreateFlags,625 flags: Io.File.CreateFlags,
626) Io.File.OpenError!Io.File {626) Io.File.OpenError!Io.File {
627 const pool: *Pool = @alignCast(@ptrCast(userdata));627 const pool: *Pool = @ptrCast(@alignCast(userdata));
628 try pool.checkCancel();628 try pool.checkCancel();
629 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };629 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
630 const fs_file = try fs_dir.createFile(sub_path, flags);630 const fs_file = try fs_dir.createFile(sub_path, flags);
...@@ -637,7 +637,7 @@ fn openFile(...@@ -637,7 +637,7 @@ fn openFile(
637 sub_path: []const u8,637 sub_path: []const u8,
638 flags: Io.File.OpenFlags,638 flags: Io.File.OpenFlags,
639) Io.File.OpenError!Io.File {639) Io.File.OpenError!Io.File {
640 const pool: *Pool = @alignCast(@ptrCast(userdata));640 const pool: *Pool = @ptrCast(@alignCast(userdata));
641 try pool.checkCancel();641 try pool.checkCancel();
642 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };642 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
643 const fs_file = try fs_dir.openFile(sub_path, flags);643 const fs_file = try fs_dir.openFile(sub_path, flags);
...@@ -645,14 +645,14 @@ fn openFile(...@@ -645,14 +645,14 @@ fn openFile(
645}645}
646646
647fn closeFile(userdata: ?*anyopaque, file: Io.File) void {647fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
648 const pool: *Pool = @alignCast(@ptrCast(userdata));648 const pool: *Pool = @ptrCast(@alignCast(userdata));
649 _ = pool;649 _ = pool;
650 const fs_file: std.fs.File = .{ .handle = file.handle };650 const fs_file: std.fs.File = .{ .handle = file.handle };
651 return fs_file.close();651 return fs_file.close();
652}652}
653653
654fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {654fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize {
655 const pool: *Pool = @alignCast(@ptrCast(userdata));655 const pool: *Pool = @ptrCast(@alignCast(userdata));
656 try pool.checkCancel();656 try pool.checkCancel();
657 const fs_file: std.fs.File = .{ .handle = file.handle };657 const fs_file: std.fs.File = .{ .handle = file.handle };
658 return switch (offset) {658 return switch (offset) {
...@@ -662,7 +662,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t...@@ -662,7 +662,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t
662}662}
663663
664fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {664fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize {
665 const pool: *Pool = @alignCast(@ptrCast(userdata));665 const pool: *Pool = @ptrCast(@alignCast(userdata));
666 try pool.checkCancel();666 try pool.checkCancel();
667 const fs_file: std.fs.File = .{ .handle = file.handle };667 const fs_file: std.fs.File = .{ .handle = file.handle };
668 return switch (offset) {668 return switch (offset) {
...@@ -672,14 +672,14 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi...@@ -672,14 +672,14 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi
672}672}
673673
674fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {674fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {
675 const pool: *Pool = @alignCast(@ptrCast(userdata));675 const pool: *Pool = @ptrCast(@alignCast(userdata));
676 try pool.checkCancel();676 try pool.checkCancel();
677 const timespec = try posix.clock_gettime(clockid);677 const timespec = try posix.clock_gettime(clockid);
678 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);678 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
679}679}
680680
681fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {681fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
682 const pool: *Pool = @alignCast(@ptrCast(userdata));682 const pool: *Pool = @ptrCast(@alignCast(userdata));
683 const deadline_nanoseconds: i96 = switch (deadline) {683 const deadline_nanoseconds: i96 = switch (deadline) {
684 .duration => |duration| duration.nanoseconds,684 .duration => |duration| duration.nanoseconds,
685 .timestamp => |timestamp| @intFromEnum(timestamp),685 .timestamp => |timestamp| @intFromEnum(timestamp),
...@@ -704,7 +704,7 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline)...@@ -704,7 +704,7 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline)
704}704}
705705
706fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {706fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
707 const pool: *Pool = @alignCast(@ptrCast(userdata));707 const pool: *Pool = @ptrCast(@alignCast(userdata));
708 _ = pool;708 _ = pool;
709709
710 var reset_event: std.Thread.ResetEvent = .{};710 var reset_event: std.Thread.ResetEvent = .{};
...@@ -736,7 +736,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {...@@ -736,7 +736,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
736}736}
737737
738fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {738fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server {
739 const pool: *Pool = @alignCast(@ptrCast(userdata));739 const pool: *Pool = @ptrCast(@alignCast(userdata));
740 try pool.checkCancel();740 try pool.checkCancel();
741741
742 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;742 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
...@@ -776,7 +776,7 @@ fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.List...@@ -776,7 +776,7 @@ fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.List
776}776}
777777
778fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {778fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection {
779 const pool: *Pool = @alignCast(@ptrCast(userdata));779 const pool: *Pool = @ptrCast(@alignCast(userdata));
780 try pool.checkCancel();780 try pool.checkCancel();
781781
782 var storage: PosixAddress = undefined;782 var storage: PosixAddress = undefined;
...@@ -788,17 +788,20 @@ fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptErr...@@ -788,17 +788,20 @@ fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptErr
788 };788 };
789}789}
790790
791fn netReadPosix(791fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {
792 userdata: ?*anyopaque,792 const pool: *Pool = @ptrCast(@alignCast(userdata));
793 stream: Io.net.Stream,
794 w: *Io.Writer,
795 limit: Io.Limit,
796) Io.net.Stream.Reader.Error!usize {
797 const pool: *Pool = @alignCast(@ptrCast(userdata));
798 try pool.checkCancel();793 try pool.checkCancel();
799794
800 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;795 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
801 const dest = try w.writableVectorPosix(&iovecs_buffer, limit);796 var i: usize = 0;
797 for (data) |buf| {
798 if (iovecs_buffer.len - i == 0) break;
799 if (buf.len != 0) {
800 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
801 i += 1;
802 }
803 }
804 const dest = iovecs_buffer[0..i];
802 assert(dest[0].len > 0);805 assert(dest[0].len > 0);
803 const n = try posix.readv(stream.handle, dest);806 const n = try posix.readv(stream.handle, dest);
804 if (n == 0) return error.EndOfStream;807 if (n == 0) return error.EndOfStream;
...@@ -812,7 +815,7 @@ fn netWritePosix(...@@ -812,7 +815,7 @@ fn netWritePosix(
812 data: []const []const u8,815 data: []const []const u8,
813 splat: usize,816 splat: usize,
814) Io.net.Stream.Writer.Error!usize {817) Io.net.Stream.Writer.Error!usize {
815 const pool: *Pool = @alignCast(@ptrCast(userdata));818 const pool: *Pool = @ptrCast(@alignCast(userdata));
816 try pool.checkCancel();819 try pool.checkCancel();
817820
818 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;821 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
...@@ -866,7 +869,7 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),...@@ -866,7 +869,7 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
866}869}
867870
868fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {871fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
869 const pool: *Pool = @alignCast(@ptrCast(userdata));872 const pool: *Pool = @ptrCast(@alignCast(userdata));
870 _ = pool;873 _ = pool;
871 const net_stream: std.net.Stream = .{ .handle = stream.handle };874 const net_stream: std.net.Stream = .{ .handle = stream.handle };
872 return net_stream.close();875 return net_stream.close();
lib/std/Io/net.zig+244-2
...@@ -2,6 +2,7 @@ const builtin = @import("builtin");...@@ -2,6 +2,7 @@ const builtin = @import("builtin");
2const native_os = builtin.os.tag;2const native_os = builtin.os.tag;
3const std = @import("../std.zig");3const std = @import("../std.zig");
4const Io = std.Io;4const Io = std.Io;
5const assert = std.debug.assert;
56
6pub const ListenError = std.net.Address.ListenError || Io.Cancelable;7pub const ListenError = std.net.Address.ListenError || Io.Cancelable;
78
...@@ -16,10 +17,233 @@ pub const ListenOptions = struct {...@@ -16,10 +17,233 @@ pub const ListenOptions = struct {
16 force_nonblocking: bool = false,17 force_nonblocking: bool = false,
17};18};
1819
20/// An already-validated host name.
21pub const HostName = struct {
22 /// Externally managed memory. Already checked to be within `max_len`.
23 bytes: []const u8,
24
25 pub const max_len = 255;
26
27 pub const InitError = error{
28 NameTooLong,
29 InvalidHostName,
30 };
31
32 pub fn init(bytes: []const u8) InitError!HostName {
33 if (bytes.len > max_len) return error.NameTooLong;
34 if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;
35 for (bytes) |byte| {
36 if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
37 continue;
38 }
39 return error.InvalidHostName;
40 }
41 return .{ .bytes = bytes };
42 }
43
44 pub const LookupOptions = struct {
45 port: u16,
46 /// Must have at least length 2.
47 addresses_buffer: []IpAddress,
48 /// If a buffer of at least `max_len` is not provided, `lookup` may
49 /// return successfully with zero-length `LookupResult.canonical_name_len`.
50 ///
51 /// Suggestion: if not interested in canonical name, pass an empty buffer;
52 /// otherwise pass a buffer of size `max_len`.
53 canonical_name_buffer: []u8,
54 /// `null` means either.
55 family: ?IpAddress.Tag = null,
56 };
57
58 pub const LookupError = Io.Cancelable || error{};
59
60 pub const LookupResult = struct {
61 /// How many `LookupOptions.addresses_buffer` elements are populated.
62 addresses_len: usize,
63 /// Length zero means no canonical name returned.
64 canonical_name_len: usize,
65 };
66
67 pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult {
68 const name = host_name.bytes;
69 assert(name.len <= max_len);
70 assert(options.addresses_buffer.len >= 2);
71
72 if (native_os == .windows) @compileError("TODO");
73 if (builtin.link_libc) @compileError("TODO");
74 if (native_os == .linux) {
75 if (options.family != .ip6) {
76 if (IpAddress.parseIp4(name, options.port)) |addr| {
77 options.addresses_buffer[0] = addr;
78 return .{ .addresses_len = 1, .canonical_name_len = 0 };
79 } else |_| {}
80 }
81 if (options.family != .ip4) {
82 if (IpAddress.parseIp6(name, options.port)) |addr| {
83 options.addresses_buffer[0] = addr;
84 return .{ .addresses_len = 1, .canonical_name_len = 0 };
85 } else |_| {}
86 }
87 {
88 const result = try lookupHosts(io, options);
89 if (result.addresses_len > 0) return sortLookupResults(options, result);
90 }
91 {
92 // RFC 6761 Section 6.3.3
93 // Name resolution APIs and libraries SHOULD recognize
94 // localhost names as special and SHOULD always return the IP
95 // loopback address for address queries and negative responses
96 // for all other query types.
97
98 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
99 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
100 if (std.mem.endsWith(u8, name, localhost) and
101 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
102 {
103 var i: usize = 0;
104 if (options.family != .ip6) {
105 options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) };
106 i += 1;
107 }
108 if (options.family != .ip4) {
109 options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) };
110 i += 1;
111 }
112 const canon_name = "localhost";
113 options.canonical_name_buffer[0..canon_name.len].* = canon_name.*;
114 return sortLookupResults(options, .{ .addresses_len = i, .canonical_name_len = canon_name.len });
115 }
116 }
117 {
118 const result = try lookupDns(io, options);
119 if (result.addresses_len > 0) return sortLookupResults(options, result);
120 }
121 return error.UnknownHostName;
122 }
123 @compileError("unimplemented");
124 }
125
126 fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult {
127 _ = options;
128 _ = result;
129 @panic("TODO");
130 }
131
132 fn lookupDns(io: Io, options: LookupOptions) !LookupResult {
133 _ = io;
134 _ = options;
135 @panic("TODO");
136 }
137
138 fn lookupHosts(io: Io, options: LookupOptions) !LookupResult {
139 const file = Io.File.openFileAbsoluteZ(io, "/etc/hosts", .{}) catch |err| switch (err) {
140 error.FileNotFound,
141 error.NotDir,
142 error.AccessDenied,
143 => return,
144 else => |e| return e,
145 };
146 defer file.close();
147
148 var line_buf: [512]u8 = undefined;
149 var file_reader = file.reader(io, &line_buf);
150 return lookupHostsReader(options, &file_reader.interface) catch |err| switch (err) {
151 error.OutOfMemory => return error.OutOfMemory,
152 error.ReadFailed => return file_reader.err.?,
153 };
154 }
155
156 fn lookupHostsReader(options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult {
157 var addresses_len: usize = 0;
158 var canonical_name_len: usize = 0;
159 while (true) {
160 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
161 error.StreamTooLong => {
162 // Skip lines that are too long.
163 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) {
164 error.EndOfStream => break,
165 error.ReadFailed => return error.ReadFailed,
166 };
167 continue;
168 },
169 error.ReadFailed => return error.ReadFailed,
170 error.EndOfStream => break,
171 };
172 var split_it = std.mem.splitScalar(u8, line, '#');
173 const no_comment_line = split_it.first();
174
175 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
176 const ip_text = line_it.next() orelse continue;
177 var first_name_text: ?[]const u8 = null;
178 while (line_it.next()) |name_text| {
179 if (std.mem.eql(u8, name_text, options.name)) {
180 if (first_name_text == null) first_name_text = name_text;
181 break;
182 }
183 } else continue;
184
185 if (canonical_name_len == 0) {
186 if (HostName.init(first_name_text)) |name_text| {
187 if (name_text.len <= options.canonical_name_buffer.len) {
188 @memcpy(options.canonical_name_buffer[0..name_text.len], name_text);
189 canonical_name_len = name_text.len;
190 }
191 }
192 }
193
194 if (options.family != .ip6) {
195 if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
196 options.addresses_buffer[addresses_len] = addr;
197 addresses_len += 1;
198 if (options.addresses_buffer.len - addresses_len == 0) return .{
199 .addresses_len = addresses_len,
200 .canonical_name_len = canonical_name_len,
201 };
202 } else |_| {}
203 }
204 if (options.family != .ip4) {
205 if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
206 options.addresses_buffer[addresses_len] = addr;
207 addresses_len += 1;
208 if (options.addresses_buffer.len - addresses_len == 0) return .{
209 .addresses_len = addresses_len,
210 .canonical_name_len = canonical_name_len,
211 };
212 } else |_| {}
213 }
214 }
215 }
216
217 pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
218
219 pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream {
220 var addresses_buffer: [32]IpAddress = undefined;
221
222 const results = try lookup(host_name, .{
223 .port = port,
224 .addresses_buffer = &addresses_buffer,
225 .canonical_name_buffer = &.{},
226 });
227 const addresses = addresses_buffer[0..results.addresses_len];
228
229 if (addresses.len == 0) return error.UnknownHostName;
230
231 for (addresses) |addr| {
232 return addr.connectTcp(io) catch |err| switch (err) {
233 error.ConnectionRefused => continue,
234 else => |e| return e,
235 };
236 }
237 return error.ConnectionRefused;
238 }
239};
240
19pub const IpAddress = union(enum) {241pub const IpAddress = union(enum) {
20 ip4: Ip4Address,242 ip4: Ip4Address,
21 ip6: Ip6Address,243 ip6: Ip6Address,
22244
245 pub const Tag = @typeInfo(IpAddress).@"union".tag_type.?;
246
23 /// Parse the given IP address string into an `IpAddress` value.247 /// Parse the given IP address string into an `IpAddress` value.
24 pub fn parse(name: []const u8, port: u16) !IpAddress {248 pub fn parse(name: []const u8, port: u16) !IpAddress {
25 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {249 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
...@@ -94,6 +318,13 @@ pub const Ip4Address = struct {...@@ -94,6 +318,13 @@ pub const Ip4Address = struct {
94 bytes: [4]u8,318 bytes: [4]u8,
95 port: u16,319 port: u16,
96320
321 pub fn localhost(port: u16) Ip4Address {
322 return .{
323 .bytes = .{ 127, 0, 0, 1 },
324 .port = port,
325 };
326 }
327
97 pub const ParseError = error{328 pub const ParseError = error{
98 Overflow,329 Overflow,
99 InvalidEnd,330 InvalidEnd,
...@@ -373,7 +604,10 @@ pub const Stream = struct {...@@ -373,7 +604,10 @@ pub const Stream = struct {
373 pub fn init(stream: Stream, buffer: []u8) Reader {604 pub fn init(stream: Stream, buffer: []u8) Reader {
374 return .{605 return .{
375 .interface = .{606 .interface = .{
376 .vtable = &.{ .stream = streamImpl },607 .vtable = &.{
608 .stream = streamImpl,
609 .readVec = readVec,
610 },
377 .buffer = buffer,611 .buffer = buffer,
378 .seek = 0,612 .seek = 0,
379 .end = 0,613 .end = 0,
...@@ -384,9 +618,17 @@ pub const Stream = struct {...@@ -384,9 +618,17 @@ pub const Stream = struct {
384 }618 }
385619
386 fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {620 fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
621 const dest = limit.slice(try io_w.writableSliceGreedy(1));
622 var data: [1][]u8 = .{dest};
623 const n = try readVec(io_r, &data);
624 io_w.advance(n);
625 return n;
626 }
627
628 fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize {
387 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));629 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
388 const io = r.io;630 const io = r.io;
389 return io.vtable.netRead(io.vtable.userdata, r.stream, io_w, limit);631 return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data);
390 }632 }
391 };633 };
392634
lib/std/http/Client.zig+14-11
...@@ -9,10 +9,10 @@ const builtin = @import("builtin");...@@ -9,10 +9,10 @@ const builtin = @import("builtin");
9const testing = std.testing;9const testing = std.testing;
10const http = std.http;10const http = std.http;
11const mem = std.mem;11const mem = std.mem;
12const net = std.net;
13const Uri = std.Uri;12const Uri = std.Uri;
14const Allocator = mem.Allocator;13const Allocator = mem.Allocator;
15const assert = std.debug.assert;14const assert = std.debug.assert;
15const Io = std.Io;
16const Writer = std.Io.Writer;16const Writer = std.Io.Writer;
17const Reader = std.Io.Reader;17const Reader = std.Io.Reader;
1818
...@@ -22,6 +22,8 @@ pub const disable_tls = std.options.http_disable_tls;...@@ -22,6 +22,8 @@ pub const disable_tls = std.options.http_disable_tls;
2222
23/// Used for all client allocations. Must be thread-safe.23/// Used for all client allocations. Must be thread-safe.
24allocator: Allocator,24allocator: Allocator,
25/// Used for opening TCP connections.
26io: Io,
2527
26ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},28ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
27ca_bundle_mutex: std.Thread.Mutex = .{},29ca_bundle_mutex: std.Thread.Mutex = .{},
...@@ -225,8 +227,8 @@ pub const Protocol = enum {...@@ -225,8 +227,8 @@ pub const Protocol = enum {
225227
226pub const Connection = struct {228pub const Connection = struct {
227 client: *Client,229 client: *Client,
228 stream_writer: net.Stream.Writer,230 stream_writer: Io.net.Stream.Writer,
229 stream_reader: net.Stream.Reader,231 stream_reader: Io.net.Stream.Reader,
230 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.232 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
231 pool_node: std.DoublyLinkedList.Node,233 pool_node: std.DoublyLinkedList.Node,
232 port: u16,234 port: u16,
...@@ -242,7 +244,7 @@ pub const Connection = struct {...@@ -242,7 +244,7 @@ pub const Connection = struct {
242 client: *Client,244 client: *Client,
243 remote_host: []const u8,245 remote_host: []const u8,
244 port: u16,246 port: u16,
245 stream: net.Stream,247 stream: Io.net.Stream,
246 ) error{OutOfMemory}!*Plain {248 ) error{OutOfMemory}!*Plain {
247 const gpa = client.allocator;249 const gpa = client.allocator;
248 const alloc_len = allocLen(client, remote_host.len);250 const alloc_len = allocLen(client, remote_host.len);
...@@ -295,7 +297,7 @@ pub const Connection = struct {...@@ -295,7 +297,7 @@ pub const Connection = struct {
295 client: *Client,297 client: *Client,
296 remote_host: []const u8,298 remote_host: []const u8,
297 port: u16,299 port: u16,
298 stream: net.Stream,300 stream: Io.net.Stream,
299 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {301 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
300 const gpa = client.allocator;302 const gpa = client.allocator;
301 const alloc_len = allocLen(client, remote_host.len);303 const alloc_len = allocLen(client, remote_host.len);
...@@ -363,7 +365,7 @@ pub const Connection = struct {...@@ -363,7 +365,7 @@ pub const Connection = struct {
363 }365 }
364 };366 };
365367
366 pub const ReadError = std.crypto.tls.Client.ReadError || std.net.Stream.ReadError;368 pub const ReadError = std.crypto.tls.Client.ReadError || Io.net.Stream.ReadError;
367369
368 pub fn getReadError(c: *const Connection) ?ReadError {370 pub fn getReadError(c: *const Connection) ?ReadError {
369 return switch (c.protocol) {371 return switch (c.protocol) {
...@@ -378,8 +380,8 @@ pub const Connection = struct {...@@ -378,8 +380,8 @@ pub const Connection = struct {
378 };380 };
379 }381 }
380382
381 fn getStream(c: *Connection) net.Stream {383 fn getStream(c: *Connection) Io.net.Stream {
382 return c.stream_reader.getStream();384 return c.stream_reader.stream;
383 }385 }
384386
385 pub fn host(c: *Connection) []u8 {387 pub fn host(c: *Connection) []u8 {
...@@ -1409,7 +1411,7 @@ pub fn connectTcp(...@@ -1409,7 +1411,7 @@ pub fn connectTcp(
1409}1411}
14101412
1411pub const ConnectTcpOptions = struct {1413pub const ConnectTcpOptions = struct {
1412 host: []const u8,1414 host: Io.net.HostName,
1413 port: u16,1415 port: u16,
1414 protocol: Protocol,1416 protocol: Protocol,
14151417
...@@ -1418,7 +1420,7 @@ pub const ConnectTcpOptions = struct {...@@ -1418,7 +1420,7 @@ pub const ConnectTcpOptions = struct {
1418};1420};
14191421
1420pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {1422pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1421 const host = options.host;1423 const host = options.host_name;
1422 const port = options.port;1424 const port = options.port;
1423 const protocol = options.protocol;1425 const protocol = options.protocol;
14241426
...@@ -1431,7 +1433,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp...@@ -1431,7 +1433,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
1431 .protocol = protocol,1433 .protocol = protocol,
1432 })) |conn| return conn;1434 })) |conn| return conn;
14331435
1434 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {1436 const stream = host.connectTcp(client.io, port) catch |err| switch (err) {
1435 error.ConnectionRefused => return error.ConnectionRefused,1437 error.ConnectionRefused => return error.ConnectionRefused,
1436 error.NetworkUnreachable => return error.NetworkUnreachable,1438 error.NetworkUnreachable => return error.NetworkUnreachable,
1437 error.ConnectionTimedOut => return error.ConnectionTimedOut,1439 error.ConnectionTimedOut => return error.ConnectionTimedOut,
...@@ -1440,6 +1442,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp...@@ -1440,6 +1442,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
1440 error.NameServerFailure => return error.NameServerFailure,1442 error.NameServerFailure => return error.NameServerFailure,
1441 error.UnknownHostName => return error.UnknownHostName,1443 error.UnknownHostName => return error.UnknownHostName,
1442 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,1444 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
1445 error.Canceled => return error.Canceled,
1443 else => return error.UnexpectedConnectFailure,1446 else => return error.UnexpectedConnectFailure,
1444 };1447 };
1445 errdefer stream.close();1448 errdefer stream.close();
lib/std/net.zig+2-9
...@@ -1461,15 +1461,8 @@ test parseHosts {...@@ -1461,15 +1461,8 @@ test parseHosts {
1461 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});1461 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
1462}1462}
14631463
1464pub fn isValidHostName(hostname: []const u8) bool {1464pub fn isValidHostName(bytes: []const u8) bool {
1465 if (hostname.len >= 254) return false;1465 _ = std.Io.net.HostName.init(bytes) catch return false;
1466 if (!std.unicode.utf8ValidateSlice(hostname)) return false;
1467 for (hostname) |byte| {
1468 if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
1469 continue;
1470 }
1471 return false;
1472 }
1473 return true;1466 return true;
1474}1467}
14751468