| author | |
| committer | |
| log | 07cc4077fba5701ef582e17c795f9f5214915179 |
| tree | 73921260ce07a499622d7c0c6ea1eda5b897a9ee |
| parent | f680c095bd96b2d739c7a1257fb3bff9abbac14a |
3 files changed, 684 insertions(+), 8 deletions(-)
lib/std/Io.zig+7| ... | @@ -559,6 +559,7 @@ const Io = @This(); | ... | @@ -559,6 +559,7 @@ const Io = @This(); |
| 559 | 559 | ||
| 560 | pub const EventLoop = @import("Io/EventLoop.zig"); | 560 | pub const EventLoop = @import("Io/EventLoop.zig"); |
| 561 | pub const ThreadPool = @import("Io/ThreadPool.zig"); | 561 | pub const ThreadPool = @import("Io/ThreadPool.zig"); |
| 562 | pub const net = @import("Io/net.zig"); | ||
| 562 | 563 | ||
| 563 | userdata: ?*anyopaque, | 564 | userdata: ?*anyopaque, |
| 564 | vtable: *const VTable, | 565 | vtable: *const VTable, |
| ... | @@ -656,6 +657,12 @@ pub const VTable = struct { | ... | @@ -656,6 +657,12 @@ pub const VTable = struct { |
| 656 | 657 | ||
| 657 | now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp, | 658 | now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp, |
| 658 | sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void, | 659 | sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void, |
| 660 | |||
| 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, | ||
| 663 | netRead: *const fn (?*anyopaque, src: net.Stream, dest: *Io.Writer, limit: Io.Limit) 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, | ||
| 665 | netClose: *const fn (?*anyopaque, stream: net.Stream) void, | ||
| 659 | }; | 666 | }; |
| 660 | 667 | ||
| 661 | pub const Cancelable = error{ | 668 | pub const Cancelable = error{ |
lib/std/Io/ThreadPool.zig+227-8| ... | @@ -3,6 +3,7 @@ const std = @import("../std.zig"); | ... | @@ -3,6 +3,7 @@ const std = @import("../std.zig"); |
| 3 | const Allocator = std.mem.Allocator; | 3 | const Allocator = std.mem.Allocator; |
| 4 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| 5 | const WaitGroup = std.Thread.WaitGroup; | 5 | const WaitGroup = std.Thread.WaitGroup; |
| 6 | const posix = std.posix; | ||
| 6 | const Io = std.Io; | 7 | const Io = std.Io; |
| 7 | const Pool = @This(); | 8 | const Pool = @This(); |
| 8 | 9 | ||
| ... | @@ -19,6 +20,9 @@ parallel_count: usize, | ... | @@ -19,6 +20,9 @@ parallel_count: usize, |
| 19 | 20 | ||
| 20 | threadlocal var current_closure: ?*AsyncClosure = null; | 21 | threadlocal var current_closure: ?*AsyncClosure = null; |
| 21 | 22 | ||
| 23 | const max_iovecs_len = 8; | ||
| 24 | const splat_buffer_size = 64; | ||
| 25 | |||
| 22 | pub const Runnable = struct { | 26 | pub const Runnable = struct { |
| 23 | start: Start, | 27 | start: Start, |
| 24 | node: std.SinglyLinkedList.Node = .{}, | 28 | node: std.SinglyLinkedList.Node = .{}, |
| ... | @@ -107,6 +111,18 @@ pub fn io(pool: *Pool) Io { | ... | @@ -107,6 +111,18 @@ pub fn io(pool: *Pool) Io { |
| 107 | 111 | ||
| 108 | .now = now, | 112 | .now = now, |
| 109 | .sleep = sleep, | 113 | .sleep = sleep, |
| 114 | |||
| 115 | .listen = listen, | ||
| 116 | .accept = accept, | ||
| 117 | .netRead = switch (builtin.os.tag) { | ||
| 118 | .windows => @panic("TODO"), | ||
| 119 | else => netReadPosix, | ||
| 120 | }, | ||
| 121 | .netWrite = switch (builtin.os.tag) { | ||
| 122 | .windows => @panic("TODO"), | ||
| 123 | else => netWritePosix, | ||
| 124 | }, | ||
| 125 | .netClose = netClose, | ||
| 110 | }, | 126 | }, |
| 111 | }; | 127 | }; |
| 112 | } | 128 | } |
| ... | @@ -461,7 +477,7 @@ fn cancel( | ... | @@ -461,7 +477,7 @@ fn cancel( |
| 461 | .linux => _ = std.os.linux.tgkill( | 477 | .linux => _ = std.os.linux.tgkill( |
| 462 | std.os.linux.getpid(), | 478 | std.os.linux.getpid(), |
| 463 | @bitCast(cancel_tid), | 479 | @bitCast(cancel_tid), |
| 464 | std.posix.SIG.IO, | 480 | posix.SIG.IO, |
| 465 | ), | 481 | ), |
| 466 | else => {}, | 482 | else => {}, |
| 467 | }, | 483 | }, |
| ... | @@ -635,7 +651,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void { | ... | @@ -635,7 +651,7 @@ fn closeFile(userdata: ?*anyopaque, file: Io.File) void { |
| 635 | return fs_file.close(); | 651 | return fs_file.close(); |
| 636 | } | 652 | } |
| 637 | 653 | ||
| 638 | fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize { | 654 | fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: posix.off_t) Io.File.PReadError!usize { |
| 639 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | 655 | const pool: *Pool = @alignCast(@ptrCast(userdata)); |
| 640 | try pool.checkCancel(); | 656 | try pool.checkCancel(); |
| 641 | const fs_file: std.fs.File = .{ .handle = file.handle }; | 657 | const fs_file: std.fs.File = .{ .handle = file.handle }; |
| ... | @@ -645,7 +661,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o | ... | @@ -645,7 +661,7 @@ fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.o |
| 645 | }; | 661 | }; |
| 646 | } | 662 | } |
| 647 | 663 | ||
| 648 | fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize { | 664 | fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posix.off_t) Io.File.PWriteError!usize { |
| 649 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | 665 | const pool: *Pool = @alignCast(@ptrCast(userdata)); |
| 650 | try pool.checkCancel(); | 666 | try pool.checkCancel(); |
| 651 | const fs_file: std.fs.File = .{ .handle = file.handle }; | 667 | const fs_file: std.fs.File = .{ .handle = file.handle }; |
| ... | @@ -655,20 +671,20 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std. | ... | @@ -655,20 +671,20 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std. |
| 655 | }; | 671 | }; |
| 656 | } | 672 | } |
| 657 | 673 | ||
| 658 | fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp { | 674 | fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp { |
| 659 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | 675 | const pool: *Pool = @alignCast(@ptrCast(userdata)); |
| 660 | try pool.checkCancel(); | 676 | try pool.checkCancel(); |
| 661 | const timespec = try std.posix.clock_gettime(clockid); | 677 | const timespec = try posix.clock_gettime(clockid); |
| 662 | 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); |
| 663 | } | 679 | } |
| 664 | 680 | ||
| 665 | fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void { | 681 | fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void { |
| 666 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | 682 | const pool: *Pool = @alignCast(@ptrCast(userdata)); |
| 667 | const deadline_nanoseconds: i96 = switch (deadline) { | 683 | const deadline_nanoseconds: i96 = switch (deadline) { |
| 668 | .duration => |duration| duration.nanoseconds, | 684 | .duration => |duration| duration.nanoseconds, |
| 669 | .timestamp => |timestamp| @intFromEnum(timestamp), | 685 | .timestamp => |timestamp| @intFromEnum(timestamp), |
| 670 | }; | 686 | }; |
| 671 | var timespec: std.posix.timespec = .{ | 687 | var timespec: posix.timespec = .{ |
| 672 | .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), | 688 | .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), |
| 673 | .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)), | 689 | .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)), |
| 674 | }; | 690 | }; |
| ... | @@ -682,7 +698,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl | ... | @@ -682,7 +698,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl |
| 682 | .FAULT => unreachable, | 698 | .FAULT => unreachable, |
| 683 | .INTR => {}, | 699 | .INTR => {}, |
| 684 | .INVAL => return error.UnsupportedClock, | 700 | .INVAL => return error.UnsupportedClock, |
| 685 | else => |err| return std.posix.unexpectedErrno(err), | 701 | else => |err| return posix.unexpectedErrno(err), |
| 686 | } | 702 | } |
| 687 | } | 703 | } |
| 688 | } | 704 | } |
| ... | @@ -718,3 +734,206 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ... | @@ -718,3 +734,206 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 718 | } | 734 | } |
| 719 | return result.?; | 735 | return result.?; |
| 720 | } | 736 | } |
| 737 | |||
| 738 | fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server { | ||
| 739 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | ||
| 740 | try pool.checkCancel(); | ||
| 741 | |||
| 742 | const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0; | ||
| 743 | const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock; | ||
| 744 | const proto: u32 = posix.IPPROTO.TCP; | ||
| 745 | const family = posixAddressFamily(address); | ||
| 746 | const sockfd = try posix.socket(family, sock_flags, proto); | ||
| 747 | const stream: std.net.Stream = .{ .handle = sockfd }; | ||
| 748 | errdefer stream.close(); | ||
| 749 | |||
| 750 | if (options.reuse_address) { | ||
| 751 | try posix.setsockopt( | ||
| 752 | sockfd, | ||
| 753 | posix.SOL.SOCKET, | ||
| 754 | posix.SO.REUSEADDR, | ||
| 755 | &std.mem.toBytes(@as(c_int, 1)), | ||
| 756 | ); | ||
| 757 | if (@hasDecl(posix.SO, "REUSEPORT") and family != posix.AF.UNIX) { | ||
| 758 | try posix.setsockopt( | ||
| 759 | sockfd, | ||
| 760 | posix.SOL.SOCKET, | ||
| 761 | posix.SO.REUSEPORT, | ||
| 762 | &std.mem.toBytes(@as(c_int, 1)), | ||
| 763 | ); | ||
| 764 | } | ||
| 765 | } | ||
| 766 | |||
| 767 | var storage: PosixAddress = undefined; | ||
| 768 | var socklen = addressToPosix(address, &storage); | ||
| 769 | try posix.bind(sockfd, &storage.any, socklen); | ||
| 770 | try posix.listen(sockfd, options.kernel_backlog); | ||
| 771 | try posix.getsockname(sockfd, &storage.any, &socklen); | ||
| 772 | return .{ | ||
| 773 | .listen_address = addressFromPosix(&storage), | ||
| 774 | .stream = .{ .handle = stream.handle }, | ||
| 775 | }; | ||
| 776 | } | ||
| 777 | |||
| 778 | fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection { | ||
| 779 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | ||
| 780 | try pool.checkCancel(); | ||
| 781 | |||
| 782 | var storage: PosixAddress = undefined; | ||
| 783 | var addr_len: posix.socklen_t = @sizeOf(PosixAddress); | ||
| 784 | const fd = try posix.accept(server.stream.handle, &storage.any, &addr_len, posix.SOCK.CLOEXEC); | ||
| 785 | return .{ | ||
| 786 | .stream = .{ .handle = fd }, | ||
| 787 | .address = addressFromPosix(&storage), | ||
| 788 | }; | ||
| 789 | } | ||
| 790 | |||
| 791 | fn netReadPosix( | ||
| 792 | userdata: ?*anyopaque, | ||
| 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(); | ||
| 799 | |||
| 800 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; | ||
| 801 | const dest = try w.writableVectorPosix(&iovecs_buffer, limit); | ||
| 802 | assert(dest[0].len > 0); | ||
| 803 | const n = try posix.readv(stream.handle, dest); | ||
| 804 | if (n == 0) return error.EndOfStream; | ||
| 805 | return n; | ||
| 806 | } | ||
| 807 | |||
| 808 | fn netWritePosix( | ||
| 809 | userdata: ?*anyopaque, | ||
| 810 | stream: Io.net.Stream, | ||
| 811 | header: []const u8, | ||
| 812 | data: []const []const u8, | ||
| 813 | splat: usize, | ||
| 814 | ) Io.net.Stream.Writer.Error!usize { | ||
| 815 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | ||
| 816 | try pool.checkCancel(); | ||
| 817 | |||
| 818 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; | ||
| 819 | var msg: posix.msghdr_const = .{ | ||
| 820 | .name = null, | ||
| 821 | .namelen = 0, | ||
| 822 | .iov = &iovecs, | ||
| 823 | .iovlen = 0, | ||
| 824 | .control = null, | ||
| 825 | .controllen = 0, | ||
| 826 | .flags = 0, | ||
| 827 | }; | ||
| 828 | addBuf(&iovecs, &msg.iovlen, header); | ||
| 829 | for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes); | ||
| 830 | const pattern = data[data.len - 1]; | ||
| 831 | if (iovecs.len - msg.iovlen != 0) switch (splat) { | ||
| 832 | 0 => {}, | ||
| 833 | 1 => addBuf(&iovecs, &msg.iovlen, pattern), | ||
| 834 | else => switch (pattern.len) { | ||
| 835 | 0 => {}, | ||
| 836 | 1 => { | ||
| 837 | var backup_buffer: [splat_buffer_size]u8 = undefined; | ||
| 838 | const splat_buffer = &backup_buffer; | ||
| 839 | const memset_len = @min(splat_buffer.len, splat); | ||
| 840 | const buf = splat_buffer[0..memset_len]; | ||
| 841 | @memset(buf, pattern[0]); | ||
| 842 | addBuf(&iovecs, &msg.iovlen, buf); | ||
| 843 | var remaining_splat = splat - buf.len; | ||
| 844 | while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) { | ||
| 845 | assert(buf.len == splat_buffer.len); | ||
| 846 | addBuf(&iovecs, &msg.iovlen, splat_buffer); | ||
| 847 | remaining_splat -= splat_buffer.len; | ||
| 848 | } | ||
| 849 | addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]); | ||
| 850 | }, | ||
| 851 | else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { | ||
| 852 | addBuf(&iovecs, &msg.iovlen, pattern); | ||
| 853 | }, | ||
| 854 | }, | ||
| 855 | }; | ||
| 856 | const flags = posix.MSG.NOSIGNAL; | ||
| 857 | return posix.sendmsg(stream.handle, &msg, flags); | ||
| 858 | } | ||
| 859 | |||
| 860 | fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void { | ||
| 861 | // OS checks ptr addr before length so zero length vectors must be omitted. | ||
| 862 | if (bytes.len == 0) return; | ||
| 863 | if (v.len - i.* == 0) return; | ||
| 864 | v[i.*] = .{ .base = bytes.ptr, .len = bytes.len }; | ||
| 865 | i.* += 1; | ||
| 866 | } | ||
| 867 | |||
| 868 | fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void { | ||
| 869 | const pool: *Pool = @alignCast(@ptrCast(userdata)); | ||
| 870 | _ = pool; | ||
| 871 | const net_stream: std.net.Stream = .{ .handle = stream.handle }; | ||
| 872 | return net_stream.close(); | ||
| 873 | } | ||
| 874 | |||
| 875 | const PosixAddress = extern union { | ||
| 876 | any: posix.sockaddr, | ||
| 877 | in: posix.sockaddr.in, | ||
| 878 | in6: posix.sockaddr.in6, | ||
| 879 | }; | ||
| 880 | |||
| 881 | fn posixAddressFamily(a: Io.net.IpAddress) posix.sa_family_t { | ||
| 882 | return switch (a) { | ||
| 883 | .ip4 => posix.AF.INET, | ||
| 884 | .ip6 => posix.AF.INET6, | ||
| 885 | }; | ||
| 886 | } | ||
| 887 | |||
| 888 | fn addressFromPosix(posix_address: *PosixAddress) Io.net.IpAddress { | ||
| 889 | return switch (posix_address.any.family) { | ||
| 890 | posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, | ||
| 891 | posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, | ||
| 892 | else => unreachable, | ||
| 893 | }; | ||
| 894 | } | ||
| 895 | |||
| 896 | fn addressToPosix(a: Io.net.IpAddress, storage: *PosixAddress) posix.socklen_t { | ||
| 897 | return switch (a) { | ||
| 898 | .ip4 => |ip4| { | ||
| 899 | storage.in = address4ToPosix(ip4); | ||
| 900 | return @sizeOf(posix.sockaddr.in); | ||
| 901 | }, | ||
| 902 | .ip6 => |ip6| { | ||
| 903 | storage.in6 = address6ToPosix(ip6); | ||
| 904 | return @sizeOf(posix.sockaddr.in6); | ||
| 905 | }, | ||
| 906 | }; | ||
| 907 | } | ||
| 908 | |||
| 909 | fn address4FromPosix(in: *posix.sockaddr.in) Io.net.Ip4Address { | ||
| 910 | return .{ | ||
| 911 | .port = std.mem.bigToNative(u16, in.port), | ||
| 912 | .bytes = @bitCast(in.addr), | ||
| 913 | }; | ||
| 914 | } | ||
| 915 | |||
| 916 | fn address6FromPosix(in6: *posix.sockaddr.in6) Io.net.Ip6Address { | ||
| 917 | return .{ | ||
| 918 | .port = std.mem.bigToNative(u16, in6.port), | ||
| 919 | .bytes = in6.addr, | ||
| 920 | .flowinfo = in6.flowinfo, | ||
| 921 | .scope_id = in6.scope_id, | ||
| 922 | }; | ||
| 923 | } | ||
| 924 | |||
| 925 | fn address4ToPosix(a: Io.net.Ip4Address) posix.sockaddr.in { | ||
| 926 | return .{ | ||
| 927 | .port = std.mem.nativeToBig(u16, a.port), | ||
| 928 | .addr = @bitCast(a.bytes), | ||
| 929 | }; | ||
| 930 | } | ||
| 931 | |||
| 932 | fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { | ||
| 933 | return .{ | ||
| 934 | .port = std.mem.nativeToBig(u16, a.port), | ||
| 935 | .flowinfo = a.flowinfo, | ||
| 936 | .addr = a.bytes, | ||
| 937 | .scope_id = a.scope_id, | ||
| 938 | }; | ||
| 939 | } |
lib/std/Io/net.zig created+450| ... | @@ -0,0 +1,450 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const native_os = builtin.os.tag; | ||
| 3 | const std = @import("../std.zig"); | ||
| 4 | const Io = std.Io; | ||
| 5 | |||
| 6 | pub const ListenError = std.net.Address.ListenError || Io.Cancelable; | ||
| 7 | |||
| 8 | pub const ListenOptions = struct { | ||
| 9 | /// How many connections the kernel will accept on the application's behalf. | ||
| 10 | /// If more than this many connections pool in the kernel, clients will start | ||
| 11 | /// seeing "Connection refused". | ||
| 12 | kernel_backlog: u31 = 128, | ||
| 13 | /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX. | ||
| 14 | /// Sets SO_REUSEADDR on Windows, which is roughly equivalent. | ||
| 15 | reuse_address: bool = false, | ||
| 16 | force_nonblocking: bool = false, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub const IpAddress = union(enum) { | ||
| 20 | ip4: Ip4Address, | ||
| 21 | ip6: Ip6Address, | ||
| 22 | |||
| 23 | /// Parse the given IP address string into an `IpAddress` value. | ||
| 24 | pub fn parse(name: []const u8, port: u16) !IpAddress { | ||
| 25 | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { | ||
| 26 | error.Overflow, | ||
| 27 | error.InvalidEnd, | ||
| 28 | error.InvalidCharacter, | ||
| 29 | error.Incomplete, | ||
| 30 | error.NonCanonical, | ||
| 31 | => {}, | ||
| 32 | } | ||
| 33 | |||
| 34 | if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) { | ||
| 35 | error.Overflow, | ||
| 36 | error.InvalidEnd, | ||
| 37 | error.InvalidCharacter, | ||
| 38 | error.Incomplete, | ||
| 39 | error.InvalidIpv4Mapping, | ||
| 40 | => {}, | ||
| 41 | } | ||
| 42 | |||
| 43 | return error.InvalidIpAddressFormat; | ||
| 44 | } | ||
| 45 | |||
| 46 | pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress { | ||
| 47 | return .{ .ip6 = try Ip6Address.parse(buffer, port) }; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress { | ||
| 51 | return .{ .ip4 = try Ip4Address.parse(buffer, port) }; | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Returns the port in native endian. | ||
| 55 | pub fn getPort(a: IpAddress) u16 { | ||
| 56 | return switch (a) { | ||
| 57 | inline .ip4, .ip6 => |x| x.port, | ||
| 58 | }; | ||
| 59 | } | ||
| 60 | |||
| 61 | /// `port` is native-endian. | ||
| 62 | pub fn setPort(a: *IpAddress, port: u16) void { | ||
| 63 | switch (a) { | ||
| 64 | inline .ip4, .ip6 => |*x| x.port = port, | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | pub fn format(a: IpAddress, w: *std.io.Writer) std.io.Writer.Error!void { | ||
| 69 | switch (a) { | ||
| 70 | .ip4, .ip6 => |x| return x.format(w), | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn eql(a: IpAddress, b: IpAddress) bool { | ||
| 75 | return switch (a) { | ||
| 76 | .ip4 => |a_ip4| switch (b) { | ||
| 77 | .ip4 => |b_ip4| a_ip4.eql(b_ip4), | ||
| 78 | else => false, | ||
| 79 | }, | ||
| 80 | .ip6 => |a_ip6| switch (b) { | ||
| 81 | .ip6 => |b_ip6| a_ip6.eql(b_ip6), | ||
| 82 | else => false, | ||
| 83 | }, | ||
| 84 | }; | ||
| 85 | } | ||
| 86 | |||
| 87 | /// The returned `Server` has an open `stream`. | ||
| 88 | pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server { | ||
| 89 | return io.vtable.listen(io.userdata, address, options); | ||
| 90 | } | ||
| 91 | }; | ||
| 92 | |||
| 93 | pub const Ip4Address = struct { | ||
| 94 | bytes: [4]u8, | ||
| 95 | port: u16, | ||
| 96 | |||
| 97 | pub const ParseError = error{ | ||
| 98 | Overflow, | ||
| 99 | InvalidEnd, | ||
| 100 | InvalidCharacter, | ||
| 101 | Incomplete, | ||
| 102 | NonCanonical, | ||
| 103 | }; | ||
| 104 | |||
| 105 | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip4Address { | ||
| 106 | var bytes: [4]u8 = @splat(0); | ||
| 107 | var index: u8 = 0; | ||
| 108 | var saw_any_digits = false; | ||
| 109 | var has_zero_prefix = false; | ||
| 110 | for (buffer) |c| switch (c) { | ||
| 111 | '.' => { | ||
| 112 | if (!saw_any_digits) return error.InvalidCharacter; | ||
| 113 | if (index == 3) return error.InvalidEnd; | ||
| 114 | index += 1; | ||
| 115 | saw_any_digits = false; | ||
| 116 | has_zero_prefix = false; | ||
| 117 | }, | ||
| 118 | '0'...'9' => { | ||
| 119 | if (c == '0' and !saw_any_digits) { | ||
| 120 | has_zero_prefix = true; | ||
| 121 | } else if (has_zero_prefix) { | ||
| 122 | return error.NonCanonical; | ||
| 123 | } | ||
| 124 | saw_any_digits = true; | ||
| 125 | bytes[index] = try std.math.mul(u8, bytes[index], 10); | ||
| 126 | bytes[index] = try std.math.add(u8, bytes[index], c - '0'); | ||
| 127 | }, | ||
| 128 | else => return error.InvalidCharacter, | ||
| 129 | }; | ||
| 130 | if (index == 3 and saw_any_digits) return .{ | ||
| 131 | .bytes = bytes, | ||
| 132 | .port = port, | ||
| 133 | }; | ||
| 134 | return error.Incomplete; | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn format(a: Ip4Address, w: *std.io.Writer) std.io.Writer.Error!void { | ||
| 138 | const bytes = &a.bytes; | ||
| 139 | try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port }); | ||
| 140 | } | ||
| 141 | |||
| 142 | pub fn eql(a: Ip4Address, b: Ip4Address) bool { | ||
| 143 | const a_int: u32 = @bitCast(a.bytes); | ||
| 144 | const b_int: u32 = @bitCast(b.bytes); | ||
| 145 | return a.port == b.port and a_int == b_int; | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | |||
| 149 | pub const Ip6Address = struct { | ||
| 150 | /// Native endian | ||
| 151 | port: u16, | ||
| 152 | /// Big endian | ||
| 153 | bytes: [16]u8, | ||
| 154 | flowinfo: u32 = 0, | ||
| 155 | scope_id: u32 = 0, | ||
| 156 | |||
| 157 | pub const ParseError = error{ | ||
| 158 | Overflow, | ||
| 159 | InvalidCharacter, | ||
| 160 | InvalidEnd, | ||
| 161 | InvalidIpv4Mapping, | ||
| 162 | Incomplete, | ||
| 163 | }; | ||
| 164 | |||
| 165 | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { | ||
| 166 | var result: Ip6Address = .{ | ||
| 167 | .port = port, | ||
| 168 | .bytes = undefined, | ||
| 169 | }; | ||
| 170 | var ip_slice: *[16]u8 = &result.bytes; | ||
| 171 | |||
| 172 | var tail: [16]u8 = undefined; | ||
| 173 | |||
| 174 | var x: u16 = 0; | ||
| 175 | var saw_any_digits = false; | ||
| 176 | var index: u8 = 0; | ||
| 177 | var scope_id = false; | ||
| 178 | var abbrv = false; | ||
| 179 | for (buffer, 0..) |c, i| { | ||
| 180 | if (scope_id) { | ||
| 181 | if (c >= '0' and c <= '9') { | ||
| 182 | const digit = c - '0'; | ||
| 183 | { | ||
| 184 | const ov = @mulWithOverflow(result.scope_id, 10); | ||
| 185 | if (ov[1] != 0) return error.Overflow; | ||
| 186 | result.scope_id = ov[0]; | ||
| 187 | } | ||
| 188 | { | ||
| 189 | const ov = @addWithOverflow(result.scope_id, digit); | ||
| 190 | if (ov[1] != 0) return error.Overflow; | ||
| 191 | result.scope_id = ov[0]; | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | return error.InvalidCharacter; | ||
| 195 | } | ||
| 196 | } else if (c == ':') { | ||
| 197 | if (!saw_any_digits) { | ||
| 198 | if (abbrv) return error.InvalidCharacter; // ':::' | ||
| 199 | if (i != 0) abbrv = true; | ||
| 200 | @memset(ip_slice[index..], 0); | ||
| 201 | ip_slice = tail[0..]; | ||
| 202 | index = 0; | ||
| 203 | continue; | ||
| 204 | } | ||
| 205 | if (index == 14) { | ||
| 206 | return error.InvalidEnd; | ||
| 207 | } | ||
| 208 | ip_slice[index] = @as(u8, @truncate(x >> 8)); | ||
| 209 | index += 1; | ||
| 210 | ip_slice[index] = @as(u8, @truncate(x)); | ||
| 211 | index += 1; | ||
| 212 | |||
| 213 | x = 0; | ||
| 214 | saw_any_digits = false; | ||
| 215 | } else if (c == '%') { | ||
| 216 | if (!saw_any_digits) { | ||
| 217 | return error.InvalidCharacter; | ||
| 218 | } | ||
| 219 | scope_id = true; | ||
| 220 | saw_any_digits = false; | ||
| 221 | } else if (c == '.') { | ||
| 222 | if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) { | ||
| 223 | // must start with '::ffff:' | ||
| 224 | return error.InvalidIpv4Mapping; | ||
| 225 | } | ||
| 226 | const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1; | ||
| 227 | const addr = (Ip4Address.parse(buffer[start_index..], 0) catch { | ||
| 228 | return error.InvalidIpv4Mapping; | ||
| 229 | }).bytes; | ||
| 230 | ip_slice = result.bytes[0..]; | ||
| 231 | ip_slice[10] = 0xff; | ||
| 232 | ip_slice[11] = 0xff; | ||
| 233 | |||
| 234 | ip_slice[12] = addr[0]; | ||
| 235 | ip_slice[13] = addr[1]; | ||
| 236 | ip_slice[14] = addr[2]; | ||
| 237 | ip_slice[15] = addr[3]; | ||
| 238 | return result; | ||
| 239 | } else { | ||
| 240 | const digit = try std.fmt.charToDigit(c, 16); | ||
| 241 | { | ||
| 242 | const ov = @mulWithOverflow(x, 16); | ||
| 243 | if (ov[1] != 0) return error.Overflow; | ||
| 244 | x = ov[0]; | ||
| 245 | } | ||
| 246 | { | ||
| 247 | const ov = @addWithOverflow(x, digit); | ||
| 248 | if (ov[1] != 0) return error.Overflow; | ||
| 249 | x = ov[0]; | ||
| 250 | } | ||
| 251 | saw_any_digits = true; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | if (!saw_any_digits and !abbrv) { | ||
| 256 | return error.Incomplete; | ||
| 257 | } | ||
| 258 | if (!abbrv and index < 14) { | ||
| 259 | return error.Incomplete; | ||
| 260 | } | ||
| 261 | |||
| 262 | if (index == 14) { | ||
| 263 | ip_slice[14] = @as(u8, @truncate(x >> 8)); | ||
| 264 | ip_slice[15] = @as(u8, @truncate(x)); | ||
| 265 | return result; | ||
| 266 | } else { | ||
| 267 | ip_slice[index] = @as(u8, @truncate(x >> 8)); | ||
| 268 | index += 1; | ||
| 269 | ip_slice[index] = @as(u8, @truncate(x)); | ||
| 270 | index += 1; | ||
| 271 | @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]); | ||
| 272 | return result; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | pub fn format(a: Ip6Address, w: *std.io.Writer) std.io.Writer.Error!void { | ||
| 277 | const bytes = &a.bytes; | ||
| 278 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { | ||
| 279 | try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{ | ||
| 280 | bytes[12], bytes[13], bytes[14], bytes[15], a.port, | ||
| 281 | }); | ||
| 282 | return; | ||
| 283 | } | ||
| 284 | const parts: [8]u16 = .{ | ||
| 285 | std.mem.readInt(u16, bytes[0..2], .big), | ||
| 286 | std.mem.readInt(u16, bytes[2..4], .big), | ||
| 287 | std.mem.readInt(u16, bytes[4..6], .big), | ||
| 288 | std.mem.readInt(u16, bytes[6..8], .big), | ||
| 289 | std.mem.readInt(u16, bytes[8..10], .big), | ||
| 290 | std.mem.readInt(u16, bytes[10..12], .big), | ||
| 291 | std.mem.readInt(u16, bytes[12..14], .big), | ||
| 292 | std.mem.readInt(u16, bytes[14..16], .big), | ||
| 293 | }; | ||
| 294 | |||
| 295 | // Find the longest zero run | ||
| 296 | var longest_start: usize = 8; | ||
| 297 | var longest_len: usize = 0; | ||
| 298 | var current_start: usize = 0; | ||
| 299 | var current_len: usize = 0; | ||
| 300 | |||
| 301 | for (parts, 0..) |part, i| { | ||
| 302 | if (part == 0) { | ||
| 303 | if (current_len == 0) { | ||
| 304 | current_start = i; | ||
| 305 | } | ||
| 306 | current_len += 1; | ||
| 307 | if (current_len > longest_len) { | ||
| 308 | longest_start = current_start; | ||
| 309 | longest_len = current_len; | ||
| 310 | } | ||
| 311 | } else { | ||
| 312 | current_len = 0; | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | // Only compress if the longest zero run is 2 or more | ||
| 317 | if (longest_len < 2) { | ||
| 318 | longest_start = 8; | ||
| 319 | longest_len = 0; | ||
| 320 | } | ||
| 321 | |||
| 322 | try w.writeAll("["); | ||
| 323 | var i: usize = 0; | ||
| 324 | var abbrv = false; | ||
| 325 | while (i < parts.len) : (i += 1) { | ||
| 326 | if (i == longest_start) { | ||
| 327 | // Emit "::" for the longest zero run | ||
| 328 | if (!abbrv) { | ||
| 329 | try w.writeAll(if (i == 0) "::" else ":"); | ||
| 330 | abbrv = true; | ||
| 331 | } | ||
| 332 | i += longest_len - 1; // Skip the compressed range | ||
| 333 | continue; | ||
| 334 | } | ||
| 335 | if (abbrv) { | ||
| 336 | abbrv = false; | ||
| 337 | } | ||
| 338 | try w.print("{x}", .{parts[i]}); | ||
| 339 | if (i != parts.len - 1) { | ||
| 340 | try w.writeAll(":"); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | try w.print("]:{d}", .{a.port}); | ||
| 344 | } | ||
| 345 | |||
| 346 | pub fn eql(a: Ip6Address, b: Ip6Address) bool { | ||
| 347 | return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes); | ||
| 348 | } | ||
| 349 | }; | ||
| 350 | |||
| 351 | pub const Stream = struct { | ||
| 352 | /// Underlying platform-defined type which may or may not be | ||
| 353 | /// interchangeable with a file system file descriptor. | ||
| 354 | handle: Handle, | ||
| 355 | |||
| 356 | pub const Handle = switch (native_os) { | ||
| 357 | .windows => std.windows.ws2_32.SOCKET, | ||
| 358 | else => std.posix.fd_t, | ||
| 359 | }; | ||
| 360 | |||
| 361 | pub fn close(s: Stream, io: Io) void { | ||
| 362 | return io.vtable.close(io.userdata, s); | ||
| 363 | } | ||
| 364 | |||
| 365 | pub const Reader = struct { | ||
| 366 | io: Io, | ||
| 367 | interface: Io.Reader, | ||
| 368 | stream: Stream, | ||
| 369 | err: ?Error, | ||
| 370 | |||
| 371 | pub const Error = std.net.Stream.ReadError || Io.Cancelable || Io.Writer.Error || error{EndOfStream}; | ||
| 372 | |||
| 373 | pub fn init(stream: Stream, buffer: []u8) Reader { | ||
| 374 | return .{ | ||
| 375 | .interface = .{ | ||
| 376 | .vtable = &.{ .stream = streamImpl }, | ||
| 377 | .buffer = buffer, | ||
| 378 | .seek = 0, | ||
| 379 | .end = 0, | ||
| 380 | }, | ||
| 381 | .stream = stream, | ||
| 382 | .err = null, | ||
| 383 | }; | ||
| 384 | } | ||
| 385 | |||
| 386 | fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { | ||
| 387 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); | ||
| 388 | const io = r.io; | ||
| 389 | return io.vtable.netRead(io.vtable.userdata, r.stream, io_w, limit); | ||
| 390 | } | ||
| 391 | }; | ||
| 392 | |||
| 393 | pub const Writer = struct { | ||
| 394 | io: Io, | ||
| 395 | interface: Io.Writer, | ||
| 396 | stream: Stream, | ||
| 397 | err: ?Error = null, | ||
| 398 | |||
| 399 | pub const Error = std.net.Stream.WriteError || Io.Cancelable; | ||
| 400 | |||
| 401 | pub fn init(stream: Stream, buffer: []u8) Writer { | ||
| 402 | return .{ | ||
| 403 | .stream = stream, | ||
| 404 | .interface = .{ | ||
| 405 | .vtable = &.{ .drain = drain }, | ||
| 406 | .buffer = buffer, | ||
| 407 | }, | ||
| 408 | }; | ||
| 409 | } | ||
| 410 | |||
| 411 | fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { | ||
| 412 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); | ||
| 413 | const io = w.io; | ||
| 414 | const buffered = io_w.buffered(); | ||
| 415 | const n = try io.vtable.netWrite(io.vtable.userdata, w.stream, buffered, data, splat); | ||
| 416 | return io_w.consume(n); | ||
| 417 | } | ||
| 418 | }; | ||
| 419 | |||
| 420 | pub fn reader(stream: Stream, buffer: []u8) Reader { | ||
| 421 | return .init(stream, buffer); | ||
| 422 | } | ||
| 423 | |||
| 424 | pub fn writer(stream: Stream, buffer: []u8) Writer { | ||
| 425 | return .init(stream, buffer); | ||
| 426 | } | ||
| 427 | }; | ||
| 428 | |||
| 429 | pub const Server = struct { | ||
| 430 | listen_address: IpAddress, | ||
| 431 | stream: Stream, | ||
| 432 | |||
| 433 | pub const Connection = struct { | ||
| 434 | stream: Stream, | ||
| 435 | address: IpAddress, | ||
| 436 | }; | ||
| 437 | |||
| 438 | pub fn deinit(s: *Server, io: Io) void { | ||
| 439 | s.stream.close(io); | ||
| 440 | s.* = undefined; | ||
| 441 | } | ||
| 442 | |||
| 443 | pub const AcceptError = std.posix.AcceptError || Io.Cancelable; | ||
| 444 | |||
| 445 | /// Blocks until a client connects to the server. The returned `Connection` has | ||
| 446 | /// an open stream. | ||
| 447 | pub fn accept(s: *Server, io: Io) AcceptError!Connection { | ||
| 448 | return io.vtable.accept(io, s); | ||
| 449 | } | ||
| 450 | }; | ||