| author | |
| committer | |
| log | 0e3930bb5a92a60de6faad4c348f062a7a420eca |
| tree | 7a750af5bb8907f281945e794c7bdce3bd8f561e |
| parent | 5622f9382c4f54d6d2cc267590dda5352718ef60 |
| parent | 96fe49ef620cfd8423e878262420f58d5983d58c |
| signature |
x/os, x/net: layout tcp, ipv4/ipv6, and socket abstractions13 files changed, 1163 insertions(+), 115 deletions(-)
lib/std/builtin.zig+1-1| ... | ... | @@ -165,7 +165,7 @@ pub const CallingConvention = enum { |
| 165 | 165 | APCS, |
| 166 | 166 | AAPCS, |
| 167 | 167 | AAPCSVFP, |
| 168 | SysV | |
| 168 | SysV, | |
| 169 | 169 | }; |
| 170 | 170 | |
| 171 | 171 | /// This data structure is used by the Zig language code generation and |
lib/std/compress/deflate.zig+2-4| ... | ... | @@ -662,14 +662,12 @@ test "lengths overflow" { |
| 662 | 662 | // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316) |
| 663 | 663 | // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6 |
| 664 | 664 | // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11 |
| 665 | const stream = [_]u8{ | |
| 666 | 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 | |
| 667 | }; | |
| 665 | const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 }; | |
| 668 | 666 | |
| 669 | 667 | const reader = std.io.fixedBufferStream(&stream).reader(); |
| 670 | 668 | var window: [0x8000]u8 = undefined; |
| 671 | 669 | var inflate = inflateStream(reader, &window); |
| 672 | 670 | |
| 673 | 671 | var buf: [1]u8 = undefined; |
| 674 | std.testing.expectError(error.InvalidLength, inflate.read(&buf)); | |
| 672 | std.testing.expectError(error.InvalidLength, inflate.read(&buf)); | |
| 675 | 673 | } |
lib/std/os/bits/darwin.zig+7| ... | ... | @@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006; |
| 832 | 832 | pub const SO_ERROR = 0x1007; |
| 833 | 833 | pub const SO_TYPE = 0x1008; |
| 834 | 834 | |
| 835 | pub const SO_NREAD = 0x1020; | |
| 836 | pub const SO_NKE = 0x1021; | |
| 837 | pub const SO_NOSIGPIPE = 0x1022; | |
| 838 | pub const SO_NOADDRERR = 0x1023; | |
| 839 | pub const SO_NWRITE = 0x1024; | |
| 840 | pub const SO_REUSESHAREUID = 0x1025; | |
| 841 | ||
| 835 | 842 | fn wstatus(x: u32) u32 { |
| 836 | 843 | return x & 0o177; |
| 837 | 844 | } |
lib/std/os/bits/linux.zig+35-4| ... | ... | @@ -32,6 +32,7 @@ pub usingnamespace @import("linux/prctl.zig"); |
| 32 | 32 | pub usingnamespace @import("linux/securebits.zig"); |
| 33 | 33 | |
| 34 | 34 | const is_mips = builtin.arch.isMIPS(); |
| 35 | const is_ppc = builtin.arch.isPPC(); | |
| 35 | 36 | const is_ppc64 = builtin.arch.isPPC64(); |
| 36 | 37 | const is_sparc = builtin.arch.isSPARC(); |
| 37 | 38 | |
| ... | ... | @@ -458,7 +459,39 @@ pub const AF_QIPCRTR = PF_QIPCRTR; |
| 458 | 459 | pub const AF_SMC = PF_SMC; |
| 459 | 460 | pub const AF_MAX = PF_MAX; |
| 460 | 461 | |
| 461 | pub usingnamespace if (!is_mips) | |
| 462 | pub usingnamespace if (is_mips) | |
| 463 | struct {} | |
| 464 | else if (is_ppc or is_ppc64) | |
| 465 | struct { | |
| 466 | pub const SO_DEBUG = 1; | |
| 467 | pub const SO_REUSEADDR = 2; | |
| 468 | pub const SO_TYPE = 3; | |
| 469 | pub const SO_ERROR = 4; | |
| 470 | pub const SO_DONTROUTE = 5; | |
| 471 | pub const SO_BROADCAST = 6; | |
| 472 | pub const SO_SNDBUF = 7; | |
| 473 | pub const SO_RCVBUF = 8; | |
| 474 | pub const SO_KEEPALIVE = 9; | |
| 475 | pub const SO_OOBINLINE = 10; | |
| 476 | pub const SO_NO_CHECK = 11; | |
| 477 | pub const SO_PRIORITY = 12; | |
| 478 | pub const SO_LINGER = 13; | |
| 479 | pub const SO_BSDCOMPAT = 14; | |
| 480 | pub const SO_REUSEPORT = 15; | |
| 481 | pub const SO_RCVLOWAT = 16; | |
| 482 | pub const SO_SNDLOWAT = 17; | |
| 483 | pub const SO_RCVTIMEO = 18; | |
| 484 | pub const SO_SNDTIMEO = 19; | |
| 485 | pub const SO_PASSCRED = 20; | |
| 486 | pub const SO_PEERCRED = 21; | |
| 487 | pub const SO_ACCEPTCONN = 30; | |
| 488 | pub const SO_PEERSEC = 31; | |
| 489 | pub const SO_SNDBUFFORCE = 32; | |
| 490 | pub const SO_RCVBUFFORCE = 33; | |
| 491 | pub const SO_PROTOCOL = 38; | |
| 492 | pub const SO_DOMAIN = 39; | |
| 493 | } | |
| 494 | else | |
| 462 | 495 | struct { |
| 463 | 496 | pub const SO_DEBUG = 1; |
| 464 | 497 | pub const SO_REUSEADDR = 2; |
| ... | ... | @@ -487,9 +520,7 @@ pub usingnamespace if (!is_mips) |
| 487 | 520 | pub const SO_RCVBUFFORCE = 33; |
| 488 | 521 | pub const SO_PROTOCOL = 38; |
| 489 | 522 | pub const SO_DOMAIN = 39; |
| 490 | } | |
| 491 | else | |
| 492 | struct {}; | |
| 523 | }; | |
| 493 | 524 | |
| 494 | 525 | pub const SO_SECURITY_AUTHENTICATION = 22; |
| 495 | 526 | pub const SO_SECURITY_ENCRYPTION_TRANSPORT = 23; |
lib/std/os/bits/linux/arm64.zig+1| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | const std = @import("../../../std.zig"); |
| 10 | 10 | const linux = std.os.linux; |
| 11 | 11 | const socklen_t = linux.socklen_t; |
| 12 | const sockaddr = linux.sockaddr; | |
| 12 | 13 | const iovec = linux.iovec; |
| 13 | 14 | const iovec_const = linux.iovec_const; |
| 14 | 15 | const uid_t = linux.uid_t; |
lib/std/os/bits/linux/riscv64.zig+5| ... | ... | @@ -376,6 +376,11 @@ pub const timespec = extern struct { |
| 376 | 376 | tv_nsec: isize, |
| 377 | 377 | }; |
| 378 | 378 | |
| 379 | pub const timeval = extern struct { | |
| 380 | tv_sec: time_t, | |
| 381 | tv_usec: i64, | |
| 382 | }; | |
| 383 | ||
| 379 | 384 | pub const Flock = extern struct { |
| 380 | 385 | l_type: i16, |
| 381 | 386 | l_whence: i16, |
lib/std/os/bits/windows.zig+5| ... | ... | @@ -22,6 +22,11 @@ pub const timespec = extern struct { |
| 22 | 22 | tv_nsec: c_long, |
| 23 | 23 | }; |
| 24 | 24 | |
| 25 | pub const timeval = extern struct { | |
| 26 | tv_sec: c_long, | |
| 27 | tv_usec: c_long, | |
| 28 | }; | |
| 29 | ||
| 25 | 30 | pub const sig_atomic_t = c_int; |
| 26 | 31 | |
| 27 | 32 | /// maximum signal number + 1 |
lib/std/x.zig+23-1| ... | ... | @@ -1 +1,23 @@ |
| 1 | pub const os = @import("x/os/os.zig"); | |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 7 | const std = @import("std.zig"); | |
| 8 | ||
| 9 | pub const os = struct { | |
| 10 | pub const Socket = @import("x/os/Socket.zig"); | |
| 11 | pub usingnamespace @import("x/os/net.zig"); | |
| 12 | }; | |
| 13 | ||
| 14 | pub const net = struct { | |
| 15 | pub const ip = @import("x/net/ip.zig"); | |
| 16 | pub const tcp = @import("x/net/tcp.zig"); | |
| 17 | }; | |
| 18 | ||
| 19 | test { | |
| 20 | inline for (.{ os, net }) |module| { | |
| 21 | std.testing.refAllDecls(module); | |
| 22 | } | |
| 23 | } |
lib/std/x/net/ip.zig created+59| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 7 | const std = @import("../../std.zig"); | |
| 8 | ||
| 9 | const IPv4 = std.x.os.IPv4; | |
| 10 | const IPv6 = std.x.os.IPv6; | |
| 11 | const Socket = std.x.os.Socket; | |
| 12 | ||
| 13 | /// A generic IP abstraction. | |
| 14 | const ip = @This(); | |
| 15 | ||
| 16 | /// A union of all eligible types of IP addresses. | |
| 17 | pub const Address = union(enum) { | |
| 18 | ipv4: IPv4.Address, | |
| 19 | ipv6: IPv6.Address, | |
| 20 | ||
| 21 | /// Instantiate a new address with a IPv4 host and port. | |
| 22 | pub fn initIPv4(host: IPv4, port: u16) Address { | |
| 23 | return .{ .ipv4 = .{ .host = host, .port = port } }; | |
| 24 | } | |
| 25 | ||
| 26 | /// Instantiate a new address with a IPv6 host and port. | |
| 27 | pub fn initIPv6(host: IPv6, port: u16) Address { | |
| 28 | return .{ .ipv6 = .{ .host = host, .port = port } }; | |
| 29 | } | |
| 30 | ||
| 31 | /// Re-interpret a generic socket address into an IP address. | |
| 32 | pub fn from(address: Socket.Address) ip.Address { | |
| 33 | return switch (address) { | |
| 34 | .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address }, | |
| 35 | .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address }, | |
| 36 | }; | |
| 37 | } | |
| 38 | ||
| 39 | /// Re-interpret an IP address into a generic socket address. | |
| 40 | pub fn into(self: ip.Address) Socket.Address { | |
| 41 | return switch (self) { | |
| 42 | .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address }, | |
| 43 | .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address }, | |
| 44 | }; | |
| 45 | } | |
| 46 | ||
| 47 | /// Implements the `std.fmt.format` API. | |
| 48 | pub fn format( | |
| 49 | self: ip.Address, | |
| 50 | comptime layout: []const u8, | |
| 51 | opts: fmt.FormatOptions, | |
| 52 | writer: anytype, | |
| 53 | ) !void { | |
| 54 | switch (self) { | |
| 55 | .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), | |
| 56 | .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), | |
| 57 | } | |
| 58 | } | |
| 59 | }; |
lib/std/x/net/tcp.zig created+344| ... | ... | @@ -0,0 +1,344 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 7 | const std = @import("../../std.zig"); | |
| 8 | ||
| 9 | const os = std.os; | |
| 10 | const ip = std.x.net.ip; | |
| 11 | ||
| 12 | const fmt = std.fmt; | |
| 13 | const mem = std.mem; | |
| 14 | const builtin = std.builtin; | |
| 15 | const testing = std.testing; | |
| 16 | ||
| 17 | const IPv4 = std.x.os.IPv4; | |
| 18 | const IPv6 = std.x.os.IPv6; | |
| 19 | const Socket = std.x.os.Socket; | |
| 20 | ||
| 21 | /// A generic TCP socket abstraction. | |
| 22 | const tcp = @This(); | |
| 23 | ||
| 24 | /// A TCP client-address pair. | |
| 25 | pub const Connection = struct { | |
| 26 | client: tcp.Client, | |
| 27 | address: ip.Address, | |
| 28 | ||
| 29 | /// Enclose a TCP client and address into a client-address pair. | |
| 30 | pub fn from(conn: Socket.Connection) tcp.Connection { | |
| 31 | return .{ | |
| 32 | .client = tcp.Client.from(conn.socket), | |
| 33 | .address = ip.Address.from(conn.address), | |
| 34 | }; | |
| 35 | } | |
| 36 | ||
| 37 | /// Unravel a TCP client-address pair into a socket-address pair. | |
| 38 | pub fn into(self: tcp.Connection) Socket.Connection { | |
| 39 | return .{ | |
| 40 | .socket = self.client.socket, | |
| 41 | .address = self.address.into(), | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | /// Closes the underlying client of the connection. | |
| 46 | pub fn deinit(self: tcp.Connection) void { | |
| 47 | self.client.deinit(); | |
| 48 | } | |
| 49 | }; | |
| 50 | ||
| 51 | /// Possible domains that a TCP client/listener may operate over. | |
| 52 | pub const Domain = extern enum(u16) { | |
| 53 | ip = os.AF_INET, | |
| 54 | ipv6 = os.AF_INET6, | |
| 55 | }; | |
| 56 | ||
| 57 | /// A TCP client. | |
| 58 | pub const Client = struct { | |
| 59 | socket: Socket, | |
| 60 | ||
| 61 | /// Opens a new client. | |
| 62 | pub fn init(domain: tcp.Domain, flags: u32) !Client { | |
| 63 | return Client{ | |
| 64 | .socket = try Socket.init( | |
| 65 | @enumToInt(domain), | |
| 66 | os.SOCK_STREAM | flags, | |
| 67 | os.IPPROTO_TCP, | |
| 68 | ), | |
| 69 | }; | |
| 70 | } | |
| 71 | ||
| 72 | /// Enclose a TCP client over an existing socket. | |
| 73 | pub fn from(socket: Socket) Client { | |
| 74 | return Client{ .socket = socket }; | |
| 75 | } | |
| 76 | ||
| 77 | /// Closes the client. | |
| 78 | pub fn deinit(self: Client) void { | |
| 79 | self.socket.deinit(); | |
| 80 | } | |
| 81 | ||
| 82 | /// Shutdown either the read side, write side, or all sides of the client's underlying socket. | |
| 83 | pub fn shutdown(self: Client, how: os.ShutdownHow) !void { | |
| 84 | return self.socket.shutdown(how); | |
| 85 | } | |
| 86 | ||
| 87 | /// Have the client attempt to the connect to an address. | |
| 88 | pub fn connect(self: Client, address: ip.Address) !void { | |
| 89 | return self.socket.connect(address.into()); | |
| 90 | } | |
| 91 | ||
| 92 | /// Read data from the socket into the buffer provided. It returns the | |
| 93 | /// number of bytes read into the buffer provided. | |
| 94 | pub fn read(self: Client, buf: []u8) !usize { | |
| 95 | return self.socket.read(buf); | |
| 96 | } | |
| 97 | ||
| 98 | /// Read data from the socket into the buffer provided with a set of flags | |
| 99 | /// specified. It returns the number of bytes read into the buffer provided. | |
| 100 | pub fn recv(self: Client, buf: []u8, flags: u32) !usize { | |
| 101 | return self.socket.recv(buf, flags); | |
| 102 | } | |
| 103 | ||
| 104 | /// Write a buffer of data provided to the socket. It returns the number | |
| 105 | /// of bytes that are written to the socket. | |
| 106 | pub fn write(self: Client, buf: []const u8) !usize { | |
| 107 | return self.socket.write(buf); | |
| 108 | } | |
| 109 | ||
| 110 | /// Writes multiple I/O vectors to the socket. It returns the number | |
| 111 | /// of bytes that are written to the socket. | |
| 112 | pub fn writev(self: Client, buffers: []const os.iovec_const) !usize { | |
| 113 | return self.socket.writev(buffers); | |
| 114 | } | |
| 115 | ||
| 116 | /// Write a buffer of data provided to the socket with a set of flags specified. | |
| 117 | /// It returns the number of bytes that are written to the socket. | |
| 118 | pub fn send(self: Client, buf: []const u8, flags: u32) !usize { | |
| 119 | return self.socket.send(buf, flags); | |
| 120 | } | |
| 121 | ||
| 122 | /// Writes multiple I/O vectors with a prepended message header to the socket | |
| 123 | /// with a set of flags specified. It returns the number of bytes that are | |
| 124 | /// written to the socket. | |
| 125 | pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize { | |
| 126 | return self.socket.sendmsg(msg, flags); | |
| 127 | } | |
| 128 | ||
| 129 | /// Query and return the latest cached error on the client's underlying socket. | |
| 130 | pub fn getError(self: Client) !void { | |
| 131 | return self.socket.getError(); | |
| 132 | } | |
| 133 | ||
| 134 | /// Query the read buffer size of the client's underlying socket. | |
| 135 | pub fn getReadBufferSize(self: Client) !u32 { | |
| 136 | return self.socket.getReadBufferSize(); | |
| 137 | } | |
| 138 | ||
| 139 | /// Query the write buffer size of the client's underlying socket. | |
| 140 | pub fn getWriteBufferSize(self: Client) !u32 { | |
| 141 | return self.socket.getWriteBufferSize(); | |
| 142 | } | |
| 143 | ||
| 144 | /// Query the address that the client's socket is locally bounded to. | |
| 145 | pub fn getLocalAddress(self: Client) !ip.Address { | |
| 146 | return ip.Address.from(try self.socket.getLocalAddress()); | |
| 147 | } | |
| 148 | ||
| 149 | /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if | |
| 150 | /// the host does not support sockets disabling Nagle's algorithm. | |
| 151 | pub fn setNoDelay(self: Client, enabled: bool) !void { | |
| 152 | if (comptime @hasDecl(os, "TCP_NODELAY")) { | |
| 153 | const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled))); | |
| 154 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes); | |
| 155 | } | |
| 156 | return error.UnsupportedSocketOption; | |
| 157 | } | |
| 158 | ||
| 159 | /// Set the write buffer size of the socket. | |
| 160 | pub fn setWriteBufferSize(self: Client, size: u32) !void { | |
| 161 | return self.socket.setWriteBufferSize(size); | |
| 162 | } | |
| 163 | ||
| 164 | /// Set the read buffer size of the socket. | |
| 165 | pub fn setReadBufferSize(self: Client, size: u32) !void { | |
| 166 | return self.socket.setReadBufferSize(size); | |
| 167 | } | |
| 168 | ||
| 169 | /// Set a timeout on the socket that is to occur if no messages are successfully written | |
| 170 | /// to its bound destination after a specified number of milliseconds. A subsequent write | |
| 171 | /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. | |
| 172 | pub fn setWriteTimeout(self: Client, milliseconds: usize) !void { | |
| 173 | return self.socket.setWriteTimeout(milliseconds); | |
| 174 | } | |
| 175 | ||
| 176 | /// Set a timeout on the socket that is to occur if no messages are successfully read | |
| 177 | /// from its bound destination after a specified number of milliseconds. A subsequent | |
| 178 | /// read from the socket will thereafter return `error.WouldBlock` should the timeout be | |
| 179 | /// exceeded. | |
| 180 | pub fn setReadTimeout(self: Client, milliseconds: usize) !void { | |
| 181 | return self.socket.setReadTimeout(milliseconds); | |
| 182 | } | |
| 183 | }; | |
| 184 | ||
| 185 | /// A TCP listener. | |
| 186 | pub const Listener = struct { | |
| 187 | socket: Socket, | |
| 188 | ||
| 189 | /// Opens a new listener. | |
| 190 | pub fn init(domain: tcp.Domain, flags: u32) !Listener { | |
| 191 | return Listener{ | |
| 192 | .socket = try Socket.init( | |
| 193 | @enumToInt(domain), | |
| 194 | os.SOCK_STREAM | flags, | |
| 195 | os.IPPROTO_TCP, | |
| 196 | ), | |
| 197 | }; | |
| 198 | } | |
| 199 | ||
| 200 | /// Closes the listener. | |
| 201 | pub fn deinit(self: Listener) void { | |
| 202 | self.socket.deinit(); | |
| 203 | } | |
| 204 | ||
| 205 | /// Shuts down the underlying listener's socket. The next subsequent call, or | |
| 206 | /// a current pending call to accept() after shutdown is called will return | |
| 207 | /// an error. | |
| 208 | pub fn shutdown(self: Listener) !void { | |
| 209 | return self.socket.shutdown(.recv); | |
| 210 | } | |
| 211 | ||
| 212 | /// Binds the listener's socket to an address. | |
| 213 | pub fn bind(self: Listener, address: ip.Address) !void { | |
| 214 | return self.socket.bind(address.into()); | |
| 215 | } | |
| 216 | ||
| 217 | /// Start listening for incoming connections. | |
| 218 | pub fn listen(self: Listener, max_backlog_size: u31) !void { | |
| 219 | return self.socket.listen(max_backlog_size); | |
| 220 | } | |
| 221 | ||
| 222 | /// Accept a pending incoming connection queued to the kernel backlog | |
| 223 | /// of the listener's socket. | |
| 224 | pub fn accept(self: Listener, flags: u32) !tcp.Connection { | |
| 225 | return tcp.Connection.from(try self.socket.accept(flags)); | |
| 226 | } | |
| 227 | ||
| 228 | /// Query and return the latest cached error on the listener's underlying socket. | |
| 229 | pub fn getError(self: Client) !void { | |
| 230 | return self.socket.getError(); | |
| 231 | } | |
| 232 | ||
| 233 | /// Query the address that the listener's socket is locally bounded to. | |
| 234 | pub fn getLocalAddress(self: Listener) !ip.Address { | |
| 235 | return ip.Address.from(try self.socket.getLocalAddress()); | |
| 236 | } | |
| 237 | ||
| 238 | /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if | |
| 239 | /// the host does not support sockets listening the same address. | |
| 240 | pub fn setReuseAddress(self: Listener, enabled: bool) !void { | |
| 241 | return self.socket.setReuseAddress(enabled); | |
| 242 | } | |
| 243 | ||
| 244 | /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if | |
| 245 | /// the host does not supports sockets listening on the same port. | |
| 246 | pub fn setReusePort(self: Listener, enabled: bool) !void { | |
| 247 | return self.socket.setReusePort(enabled); | |
| 248 | } | |
| 249 | ||
| 250 | /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not | |
| 251 | /// support TCP Fast Open. | |
| 252 | pub fn setFastOpen(self: Listener, enabled: bool) !void { | |
| 253 | if (comptime @hasDecl(os, "TCP_FASTOPEN")) { | |
| 254 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 255 | } | |
| 256 | return error.UnsupportedSocketOption; | |
| 257 | } | |
| 258 | ||
| 259 | /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns | |
| 260 | /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. | |
| 261 | pub fn setQuickACK(self: Listener, enabled: bool) !void { | |
| 262 | if (comptime @hasDecl(os, "TCP_QUICKACK")) { | |
| 263 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 264 | } | |
| 265 | return error.UnsupportedSocketOption; | |
| 266 | } | |
| 267 | ||
| 268 | /// Set a timeout on the listener that is to occur if no new incoming connections come in | |
| 269 | /// after a specified number of milliseconds. A subsequent accept call to the listener | |
| 270 | /// will thereafter return `error.WouldBlock` should the timeout be exceeded. | |
| 271 | pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void { | |
| 272 | return self.socket.setReadTimeout(milliseconds); | |
| 273 | } | |
| 274 | }; | |
| 275 | ||
| 276 | test "tcp: create client/listener pair" { | |
| 277 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 278 | ||
| 279 | const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); | |
| 280 | defer listener.deinit(); | |
| 281 | ||
| 282 | try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); | |
| 283 | try listener.listen(128); | |
| 284 | ||
| 285 | const binded_address = try listener.getLocalAddress(); | |
| 286 | ||
| 287 | const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC); | |
| 288 | defer client.deinit(); | |
| 289 | ||
| 290 | try client.connect(binded_address); | |
| 291 | ||
| 292 | const conn = try listener.accept(os.SOCK_CLOEXEC); | |
| 293 | defer conn.deinit(); | |
| 294 | } | |
| 295 | ||
| 296 | test "tcp/client: set read timeout of 1 millisecond on blocking client" { | |
| 297 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 298 | ||
| 299 | const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); | |
| 300 | defer listener.deinit(); | |
| 301 | ||
| 302 | try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); | |
| 303 | try listener.listen(128); | |
| 304 | ||
| 305 | const binded_address = try listener.getLocalAddress(); | |
| 306 | ||
| 307 | const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC); | |
| 308 | defer client.deinit(); | |
| 309 | ||
| 310 | try client.connect(binded_address); | |
| 311 | try client.setReadTimeout(1); | |
| 312 | ||
| 313 | const conn = try listener.accept(os.SOCK_CLOEXEC); | |
| 314 | defer conn.deinit(); | |
| 315 | ||
| 316 | var buf: [1]u8 = undefined; | |
| 317 | testing.expectError(error.WouldBlock, client.read(&buf)); | |
| 318 | } | |
| 319 | ||
| 320 | test "tcp/listener: bind to unspecified ipv4 address" { | |
| 321 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 322 | ||
| 323 | const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); | |
| 324 | defer listener.deinit(); | |
| 325 | ||
| 326 | try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); | |
| 327 | try listener.listen(128); | |
| 328 | ||
| 329 | const address = try listener.getLocalAddress(); | |
| 330 | testing.expect(address == .ipv4); | |
| 331 | } | |
| 332 | ||
| 333 | test "tcp/listener: bind to unspecified ipv6 address" { | |
| 334 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 335 | ||
| 336 | const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC); | |
| 337 | defer listener.deinit(); | |
| 338 | ||
| 339 | try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0)); | |
| 340 | try listener.listen(128); | |
| 341 | ||
| 342 | const address = try listener.getLocalAddress(); | |
| 343 | testing.expect(address == .ipv6); | |
| 344 | } |
lib/std/x/os/Socket.zig+114-96| ... | ... | @@ -1,18 +1,109 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 1 | 7 | const std = @import("../../std.zig"); |
| 8 | const net = @import("net.zig"); | |
| 2 | 9 | |
| 3 | 10 | const os = std.os; |
| 4 | 11 | const mem = std.mem; |
| 5 | const net = std.net; | |
| 6 | 12 | const time = std.time; |
| 7 | const builtin = std.builtin; | |
| 8 | const testing = std.testing; | |
| 9 | 13 | |
| 14 | /// A generic socket abstraction. | |
| 10 | 15 | const Socket = @This(); |
| 11 | 16 | |
| 12 | 17 | /// A socket-address pair. |
| 13 | 18 | pub const Connection = struct { |
| 14 | 19 | socket: Socket, |
| 15 | address: net.Address, | |
| 20 | address: Socket.Address, | |
| 21 | ||
| 22 | /// Enclose a socket and address into a socket-address pair. | |
| 23 | pub fn from(socket: Socket, address: Socket.Address) Socket.Connection { | |
| 24 | return .{ .socket = socket, .address = address }; | |
| 25 | } | |
| 26 | }; | |
| 27 | ||
| 28 | /// A generic socket address abstraction. It is safe to directly access and modify | |
| 29 | /// the fields of a `Socket.Address`. | |
| 30 | pub const Address = union(enum) { | |
| 31 | ipv4: net.IPv4.Address, | |
| 32 | ipv6: net.IPv6.Address, | |
| 33 | ||
| 34 | /// Instantiate a new address with a IPv4 host and port. | |
| 35 | pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address { | |
| 36 | return .{ .ipv4 = .{ .host = host, .port = port } }; | |
| 37 | } | |
| 38 | ||
| 39 | /// Instantiate a new address with a IPv6 host and port. | |
| 40 | pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address { | |
| 41 | return .{ .ipv6 = .{ .host = host, .port = port } }; | |
| 42 | } | |
| 43 | ||
| 44 | /// Parses a `sockaddr` into a generic socket address. | |
| 45 | pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address { | |
| 46 | switch (address.family) { | |
| 47 | os.AF_INET => { | |
| 48 | const info = @ptrCast(*const os.sockaddr_in, address); | |
| 49 | const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) }; | |
| 50 | const port = mem.bigToNative(u16, info.port); | |
| 51 | return Socket.Address.initIPv4(host, port); | |
| 52 | }, | |
| 53 | os.AF_INET6 => { | |
| 54 | const info = @ptrCast(*const os.sockaddr_in6, address); | |
| 55 | const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id }; | |
| 56 | const port = mem.bigToNative(u16, info.port); | |
| 57 | return Socket.Address.initIPv6(host, port); | |
| 58 | }, | |
| 59 | else => unreachable, | |
| 60 | } | |
| 61 | } | |
| 62 | ||
| 63 | /// Encodes a generic socket address into an extern union that may be reliably | |
| 64 | /// casted into a `sockaddr` which may be passed into socket syscalls. | |
| 65 | pub fn toNative(self: Socket.Address) extern union { | |
| 66 | ipv4: os.sockaddr_in, | |
| 67 | ipv6: os.sockaddr_in6, | |
| 68 | } { | |
| 69 | return switch (self) { | |
| 70 | .ipv4 => |address| .{ | |
| 71 | .ipv4 = .{ | |
| 72 | .addr = @bitCast(u32, address.host.octets), | |
| 73 | .port = mem.nativeToBig(u16, address.port), | |
| 74 | }, | |
| 75 | }, | |
| 76 | .ipv6 => |address| .{ | |
| 77 | .ipv6 = .{ | |
| 78 | .addr = address.host.octets, | |
| 79 | .port = mem.nativeToBig(u16, address.port), | |
| 80 | .scope_id = address.host.scope_id, | |
| 81 | .flowinfo = 0, | |
| 82 | }, | |
| 83 | }, | |
| 84 | }; | |
| 85 | } | |
| 86 | ||
| 87 | /// Returns the number of bytes that make up the `sockaddr` equivalent to the address. | |
| 88 | pub fn getNativeSize(self: Socket.Address) u32 { | |
| 89 | return switch (self) { | |
| 90 | .ipv4 => @sizeOf(os.sockaddr_in), | |
| 91 | .ipv6 => @sizeOf(os.sockaddr_in6), | |
| 92 | }; | |
| 93 | } | |
| 94 | ||
| 95 | /// Implements the `std.fmt.format` API. | |
| 96 | pub fn format( | |
| 97 | self: Socket.Address, | |
| 98 | comptime layout: []const u8, | |
| 99 | opts: fmt.FormatOptions, | |
| 100 | writer: anytype, | |
| 101 | ) !void { | |
| 102 | switch (self) { | |
| 103 | .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), | |
| 104 | .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }), | |
| 105 | } | |
| 106 | } | |
| 16 | 107 | }; |
| 17 | 108 | |
| 18 | 109 | /// The underlying handle of a socket. |
| ... | ... | @@ -23,19 +114,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { |
| 23 | 114 | return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; |
| 24 | 115 | } |
| 25 | 116 | |
| 117 | /// Enclose a socket abstraction over an existing socket file descriptor. | |
| 118 | pub fn from(fd: os.socket_t) Socket { | |
| 119 | return Socket{ .fd = fd }; | |
| 120 | } | |
| 121 | ||
| 26 | 122 | /// Closes the socket. |
| 27 | 123 | pub fn deinit(self: Socket) void { |
| 28 | 124 | os.closeSocket(self.fd); |
| 29 | 125 | } |
| 30 | 126 | |
| 31 | /// Shutdown either the read side, or write side, or the entirety of a socket. | |
| 127 | /// Shutdown either the read side, write side, or all side of the socket. | |
| 32 | 128 | pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { |
| 33 | 129 | return os.shutdown(self.fd, how); |
| 34 | 130 | } |
| 35 | 131 | |
| 36 | 132 | /// Binds the socket to an address. |
| 37 | pub fn bind(self: Socket, address: net.Address) !void { | |
| 38 | return os.bind(self.fd, &address.any, address.getOsSockLen()); | |
| 133 | pub fn bind(self: Socket, address: Socket.Address) !void { | |
| 134 | return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); | |
| 39 | 135 | } |
| 40 | 136 | |
| 41 | 137 | /// Start listening for incoming connections on the socket. |
| ... | ... | @@ -44,8 +140,8 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void { |
| 44 | 140 | } |
| 45 | 141 | |
| 46 | 142 | /// Have the socket attempt to the connect to an address. |
| 47 | pub fn connect(self: Socket, address: net.Address) !void { | |
| 48 | return os.connect(self.fd, &address.any, address.getOsSockLen()); | |
| 143 | pub fn connect(self: Socket, address: Socket.Address) !void { | |
| 144 | return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); | |
| 49 | 145 | } |
| 50 | 146 | |
| 51 | 147 | /// Accept a pending incoming connection queued to the kernel backlog |
| ... | ... | @@ -54,12 +150,10 @@ pub fn accept(self: Socket, flags: u32) !Socket.Connection { |
| 54 | 150 | var address: os.sockaddr = undefined; |
| 55 | 151 | var address_len: u32 = @sizeOf(os.sockaddr); |
| 56 | 152 | |
| 57 | const fd = try os.accept(self.fd, &address, &address_len, flags); | |
| 153 | const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) }; | |
| 154 | const socket_address = Socket.Address.fromNative(@alignCast(4, &address)); | |
| 58 | 155 | |
| 59 | return Connection{ | |
| 60 | .socket = Socket{ .fd = fd }, | |
| 61 | .address = net.Address.initPosix(@alignCast(4, &address)), | |
| 62 | }; | |
| 156 | return Socket.Connection.from(socket, socket_address); | |
| 63 | 157 | } |
| 64 | 158 | |
| 65 | 159 | /// Read data from the socket into the buffer provided. It returns the |
| ... | ... | @@ -100,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize { |
| 100 | 194 | } |
| 101 | 195 | |
| 102 | 196 | /// Query the address that the socket is locally bounded to. |
| 103 | pub fn getLocalAddress(self: Socket) !net.Address { | |
| 197 | pub fn getLocalAddress(self: Socket) !Socket.Address { | |
| 104 | 198 | var address: os.sockaddr = undefined; |
| 105 | 199 | var address_len: u32 = @sizeOf(os.sockaddr); |
| 106 | 200 | try os.getsockname(self.fd, &address, &address_len); |
| 107 | return net.Address.initPosix(@alignCast(4, &address)); | |
| 201 | return Socket.Address.fromNative(@alignCast(4, &address)); | |
| 108 | 202 | } |
| 109 | 203 | |
| 110 | 204 | /// Query and return the latest cached error on the socket. |
| ... | ... | @@ -164,33 +258,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void { |
| 164 | 258 | return error.UnsupportedSocketOption; |
| 165 | 259 | } |
| 166 | 260 | |
| 167 | /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support | |
| 168 | /// sockets disabling Nagle's algorithm. | |
| 169 | pub fn setNoDelay(self: Socket, enabled: bool) !void { | |
| 170 | if (comptime @hasDecl(os, "TCP_NODELAY")) { | |
| 171 | return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 172 | } | |
| 173 | return error.UnsupportedSocketOption; | |
| 174 | } | |
| 175 | ||
| 176 | /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not | |
| 177 | /// support TCP Fast Open. | |
| 178 | pub fn setFastOpen(self: Socket, enabled: bool) !void { | |
| 179 | if (comptime @hasDecl(os, "TCP_FASTOPEN")) { | |
| 180 | return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 181 | } | |
| 182 | return error.UnsupportedSocketOption; | |
| 183 | } | |
| 184 | ||
| 185 | /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns | |
| 186 | /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. | |
| 187 | pub fn setQuickACK(self: Socket, enabled: bool) !void { | |
| 188 | if (comptime @hasDecl(os, "TCP_QUICKACK")) { | |
| 189 | return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 190 | } | |
| 191 | return error.UnsupportedSocketOption; | |
| 192 | } | |
| 193 | ||
| 194 | 261 | /// Set the write buffer size of the socket. |
| 195 | 262 | pub fn setWriteBufferSize(self: Socket, size: u32) !void { |
| 196 | 263 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); |
| ... | ... | @@ -206,8 +273,8 @@ pub fn setReadBufferSize(self: Socket, size: u32) !void { |
| 206 | 273 | /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. |
| 207 | 274 | pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { |
| 208 | 275 | const timeout = os.timeval{ |
| 209 | .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), | |
| 210 | .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), | |
| 276 | .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), | |
| 277 | .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), | |
| 211 | 278 | }; |
| 212 | 279 | |
| 213 | 280 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout)); |
| ... | ... | @@ -219,58 +286,9 @@ pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { |
| 219 | 286 | /// exceeded. |
| 220 | 287 | pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { |
| 221 | 288 | const timeout = os.timeval{ |
| 222 | .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), | |
| 223 | .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), | |
| 289 | .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), | |
| 290 | .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), | |
| 224 | 291 | }; |
| 225 | 292 | |
| 226 | 293 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); |
| 227 | 294 | } |
| 228 | ||
| 229 | test { | |
| 230 | testing.refAllDecls(@This()); | |
| 231 | } | |
| 232 | ||
| 233 | test "socket/linux: set read timeout of 1 millisecond on blocking socket" { | |
| 234 | if (builtin.os.tag != .linux) return error.SkipZigTest; | |
| 235 | ||
| 236 | const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); | |
| 237 | defer a.deinit(); | |
| 238 | ||
| 239 | try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); | |
| 240 | try a.listen(128); | |
| 241 | ||
| 242 | const binded_address = try a.getLocalAddress(); | |
| 243 | ||
| 244 | const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); | |
| 245 | defer b.deinit(); | |
| 246 | ||
| 247 | try b.connect(binded_address); | |
| 248 | try b.setReadTimeout(1); | |
| 249 | ||
| 250 | const ab = try a.accept(os.SOCK_CLOEXEC); | |
| 251 | defer ab.socket.deinit(); | |
| 252 | ||
| 253 | var buf: [1]u8 = undefined; | |
| 254 | testing.expectError(error.WouldBlock, b.read(&buf)); | |
| 255 | } | |
| 256 | ||
| 257 | test "socket/linux: create non-blocking socket pair" { | |
| 258 | if (builtin.os.tag != .linux) return error.SkipZigTest; | |
| 259 | ||
| 260 | const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); | |
| 261 | defer a.deinit(); | |
| 262 | ||
| 263 | try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); | |
| 264 | try a.listen(128); | |
| 265 | ||
| 266 | const binded_address = try a.getLocalAddress(); | |
| 267 | ||
| 268 | const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); | |
| 269 | defer b.deinit(); | |
| 270 | ||
| 271 | testing.expectError(error.WouldBlock, b.connect(binded_address)); | |
| 272 | try b.getError(); | |
| 273 | ||
| 274 | const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); | |
| 275 | defer ab.socket.deinit(); | |
| 276 | } |
lib/std/x/os/net.zig created+567| ... | ... | @@ -0,0 +1,567 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2021 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | ||
| 7 | const std = @import("../../std.zig"); | |
| 8 | ||
| 9 | const os = std.os; | |
| 10 | const fmt = std.fmt; | |
| 11 | const mem = std.mem; | |
| 12 | const math = std.math; | |
| 13 | const builtin = std.builtin; | |
| 14 | const testing = std.testing; | |
| 15 | ||
| 16 | /// Resolves a network interface name into a scope/zone ID. It returns | |
| 17 | /// an error if either resolution fails, or if the interface name is | |
| 18 | /// too long. | |
| 19 | pub fn resolveScopeID(name: []const u8) !u32 { | |
| 20 | if (comptime @hasDecl(os, "IFNAMESIZE")) { | |
| 21 | if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong; | |
| 22 | ||
| 23 | const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0); | |
| 24 | defer os.closeSocket(fd); | |
| 25 | ||
| 26 | var f: os.ifreq = undefined; | |
| 27 | mem.copy(u8, &f.ifrn.name, name); | |
| 28 | f.ifrn.name[name.len] = 0; | |
| 29 | ||
| 30 | try os.ioctl_SIOCGIFINDEX(fd, &f); | |
| 31 | ||
| 32 | return @bitCast(u32, f.ifru.ivalue); | |
| 33 | } | |
| 34 | return error.Unsupported; | |
| 35 | } | |
| 36 | ||
| 37 | /// An IPv4 address comprised of 4 bytes. | |
| 38 | pub const IPv4 = extern struct { | |
| 39 | /// A IPv4 host-port pair. | |
| 40 | pub const Address = extern struct { | |
| 41 | host: IPv4, | |
| 42 | port: u16, | |
| 43 | }; | |
| 44 | ||
| 45 | /// Octets of a IPv4 address designating the local host. | |
| 46 | pub const localhost_octets = [_]u8{ 127, 0, 0, 1 }; | |
| 47 | ||
| 48 | /// The IPv4 address of the local host. | |
| 49 | pub const localhost: IPv4 = .{ .octets = localhost_octets }; | |
| 50 | ||
| 51 | /// Octets of an unspecified IPv4 address. | |
| 52 | pub const unspecified_octets = [_]u8{0} ** 4; | |
| 53 | ||
| 54 | /// An unspecified IPv4 address. | |
| 55 | pub const unspecified: IPv4 = .{ .octets = unspecified_octets }; | |
| 56 | ||
| 57 | /// Octets of a broadcast IPv4 address. | |
| 58 | pub const broadcast_octets = [_]u8{255} ** 4; | |
| 59 | ||
| 60 | /// An IPv4 broadcast address. | |
| 61 | pub const broadcast: IPv4 = .{ .octets = broadcast_octets }; | |
| 62 | ||
| 63 | /// The prefix octet pattern of a link-local IPv4 address. | |
| 64 | pub const link_local_prefix = [_]u8{ 169, 254 }; | |
| 65 | ||
| 66 | /// The prefix octet patterns of IPv4 addresses intended for | |
| 67 | /// documentation. | |
| 68 | pub const documentation_prefixes = [_][]const u8{ | |
| 69 | &[_]u8{ 192, 0, 2 }, | |
| 70 | &[_]u8{ 198, 51, 100 }, | |
| 71 | &[_]u8{ 203, 0, 113 }, | |
| 72 | }; | |
| 73 | ||
| 74 | octets: [4]u8, | |
| 75 | ||
| 76 | /// Returns whether or not the two addresses are equal to, less than, or | |
| 77 | /// greater than each other. | |
| 78 | pub fn cmp(self: IPv4, other: IPv4) math.Order { | |
| 79 | return mem.order(u8, &self.octets, &other.octets); | |
| 80 | } | |
| 81 | ||
| 82 | /// Returns true if both addresses are semantically equivalent. | |
| 83 | pub fn eql(self: IPv4, other: IPv4) bool { | |
| 84 | return mem.eql(u8, &self.octets, &other.octets); | |
| 85 | } | |
| 86 | ||
| 87 | /// Returns true if the address is a loopback address. | |
| 88 | pub fn isLoopback(self: IPv4) bool { | |
| 89 | return self.octets[0] == 127; | |
| 90 | } | |
| 91 | ||
| 92 | /// Returns true if the address is an unspecified IPv4 address. | |
| 93 | pub fn isUnspecified(self: IPv4) bool { | |
| 94 | return mem.eql(u8, &self.octets, &unspecified_octets); | |
| 95 | } | |
| 96 | ||
| 97 | /// Returns true if the address is a private IPv4 address. | |
| 98 | pub fn isPrivate(self: IPv4) bool { | |
| 99 | return self.octets[0] == 10 or | |
| 100 | (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or | |
| 101 | (self.octets[0] == 192 and self.octets[1] == 168); | |
| 102 | } | |
| 103 | ||
| 104 | /// Returns true if the address is a link-local IPv4 address. | |
| 105 | pub fn isLinkLocal(self: IPv4) bool { | |
| 106 | return mem.startsWith(u8, &self.octets, &link_local_prefix); | |
| 107 | } | |
| 108 | ||
| 109 | /// Returns true if the address is a multicast IPv4 address. | |
| 110 | pub fn isMulticast(self: IPv4) bool { | |
| 111 | return self.octets[0] >= 224 and self.octets[0] <= 239; | |
| 112 | } | |
| 113 | ||
| 114 | /// Returns true if the address is a IPv4 broadcast address. | |
| 115 | pub fn isBroadcast(self: IPv4) bool { | |
| 116 | return mem.eql(u8, &self.octets, &broadcast_octets); | |
| 117 | } | |
| 118 | ||
| 119 | /// Returns true if the address is in a range designated for documentation. Refer | |
| 120 | /// to IETF RFC 5737 for more details. | |
| 121 | pub fn isDocumentation(self: IPv4) bool { | |
| 122 | inline for (documentation_prefixes) |prefix| { | |
| 123 | if (mem.startsWith(u8, &self.octets, prefix)) { | |
| 124 | return true; | |
| 125 | } | |
| 126 | } | |
| 127 | return false; | |
| 128 | } | |
| 129 | ||
| 130 | /// Implements the `std.fmt.format` API. | |
| 131 | pub fn format( | |
| 132 | self: IPv4, | |
| 133 | comptime layout: []const u8, | |
| 134 | opts: fmt.FormatOptions, | |
| 135 | writer: anytype, | |
| 136 | ) !void { | |
| 137 | if (comptime layout.len != 0 and layout[0] != 's') { | |
| 138 | @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'."); | |
| 139 | } | |
| 140 | ||
| 141 | try fmt.format(writer, "{}.{}.{}.{}", .{ | |
| 142 | self.octets[0], | |
| 143 | self.octets[1], | |
| 144 | self.octets[2], | |
| 145 | self.octets[3], | |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | /// Set of possible errors that may encountered when parsing an IPv4 | |
| 150 | /// address. | |
| 151 | pub const ParseError = error{ | |
| 152 | UnexpectedEndOfOctet, | |
| 153 | TooManyOctets, | |
| 154 | OctetOverflow, | |
| 155 | UnexpectedToken, | |
| 156 | IncompleteAddress, | |
| 157 | }; | |
| 158 | ||
| 159 | /// Parses an arbitrary IPv4 address. | |
| 160 | pub fn parse(buf: []const u8) ParseError!IPv4 { | |
| 161 | var octets: [4]u8 = undefined; | |
| 162 | var octet: u8 = 0; | |
| 163 | ||
| 164 | var index: u8 = 0; | |
| 165 | var saw_any_digits: bool = false; | |
| 166 | ||
| 167 | for (buf) |c| { | |
| 168 | switch (c) { | |
| 169 | '.' => { | |
| 170 | if (!saw_any_digits) return error.UnexpectedEndOfOctet; | |
| 171 | if (index == 3) return error.TooManyOctets; | |
| 172 | octets[index] = octet; | |
| 173 | index += 1; | |
| 174 | octet = 0; | |
| 175 | saw_any_digits = false; | |
| 176 | }, | |
| 177 | '0'...'9' => { | |
| 178 | saw_any_digits = true; | |
| 179 | octet = math.mul(u8, octet, 10) catch return error.OctetOverflow; | |
| 180 | octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow; | |
| 181 | }, | |
| 182 | else => return error.UnexpectedToken, | |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | if (index == 3 and saw_any_digits) { | |
| 187 | octets[index] = octet; | |
| 188 | return IPv4{ .octets = octets }; | |
| 189 | } | |
| 190 | ||
| 191 | return error.IncompleteAddress; | |
| 192 | } | |
| 193 | ||
| 194 | /// Maps the address to its IPv6 equivalent. In most cases, you would | |
| 195 | /// want to map the address to its IPv6 equivalent rather than directly | |
| 196 | /// re-interpreting the address. | |
| 197 | pub fn mapToIPv6(self: IPv4) IPv6 { | |
| 198 | var octets: [16]u8 = undefined; | |
| 199 | mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix); | |
| 200 | mem.copy(u8, octets[12..], &self.octets); | |
| 201 | return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; | |
| 202 | } | |
| 203 | ||
| 204 | /// Directly re-interprets the address to its IPv6 equivalent. In most | |
| 205 | /// cases, you would want to map the address to its IPv6 equivalent rather | |
| 206 | /// than directly re-interpreting the address. | |
| 207 | pub fn toIPv6(self: IPv4) IPv6 { | |
| 208 | var octets: [16]u8 = undefined; | |
| 209 | mem.set(u8, octets[0..12], 0); | |
| 210 | mem.copy(u8, octets[12..], &self.octets); | |
| 211 | return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; | |
| 212 | } | |
| 213 | }; | |
| 214 | ||
| 215 | /// An IPv6 address comprised of 16 bytes for an address, and 4 bytes | |
| 216 | /// for a scope ID; cumulatively summing to 20 bytes in total. | |
| 217 | pub const IPv6 = extern struct { | |
| 218 | /// A IPv6 host-port pair. | |
| 219 | pub const Address = extern struct { | |
| 220 | host: IPv6, | |
| 221 | port: u16, | |
| 222 | }; | |
| 223 | ||
| 224 | /// Octets of a IPv6 address designating the local host. | |
| 225 | pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; | |
| 226 | ||
| 227 | /// The IPv6 address of the local host. | |
| 228 | pub const localhost: IPv6 = .{ | |
| 229 | .octets = localhost_octets, | |
| 230 | .scope_id = no_scope_id, | |
| 231 | }; | |
| 232 | ||
| 233 | /// Octets of an unspecified IPv6 address. | |
| 234 | pub const unspecified_octets = [_]u8{0} ** 16; | |
| 235 | ||
| 236 | /// An unspecified IPv6 address. | |
| 237 | pub const unspecified: IPv6 = .{ | |
| 238 | .octets = unspecified_octets, | |
| 239 | .scope_id = no_scope_id, | |
| 240 | }; | |
| 241 | ||
| 242 | /// The prefix of a IPv6 address that is mapped to a IPv4 address. | |
| 243 | pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2; | |
| 244 | ||
| 245 | /// A marker value used to designate an IPv6 address with no | |
| 246 | /// associated scope ID. | |
| 247 | pub const no_scope_id = math.maxInt(u32); | |
| 248 | ||
| 249 | octets: [16]u8, | |
| 250 | scope_id: u32, | |
| 251 | ||
| 252 | /// Returns whether or not the two addresses are equal to, less than, or | |
| 253 | /// greater than each other. | |
| 254 | pub fn cmp(self: IPv6, other: IPv6) math.Order { | |
| 255 | return switch (mem.order(u8, self.octets, other.octets)) { | |
| 256 | .eq => math.order(self.scope_id, other.scope_id), | |
| 257 | else => |order| order, | |
| 258 | }; | |
| 259 | } | |
| 260 | ||
| 261 | /// Returns true if both addresses are semantically equivalent. | |
| 262 | pub fn eql(self: IPv6, other: IPv6) bool { | |
| 263 | return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets); | |
| 264 | } | |
| 265 | ||
| 266 | /// Returns true if the address is an unspecified IPv6 address. | |
| 267 | pub fn isUnspecified(self: IPv6) bool { | |
| 268 | return mem.eql(u8, &self.octets, &unspecified_octets); | |
| 269 | } | |
| 270 | ||
| 271 | /// Returns true if the address is a loopback address. | |
| 272 | pub fn isLoopback(self: IPv6) bool { | |
| 273 | return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and | |
| 274 | mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 }); | |
| 275 | } | |
| 276 | ||
| 277 | /// Returns true if the address maps to an IPv4 address. | |
| 278 | pub fn mapsToIPv4(self: IPv6) bool { | |
| 279 | return mem.startsWith(u8, &self.octets, &v4_mapped_prefix); | |
| 280 | } | |
| 281 | ||
| 282 | /// Returns an IPv4 address representative of the address should | |
| 283 | /// it the address be mapped to an IPv4 address. It returns null | |
| 284 | /// otherwise. | |
| 285 | pub fn toIPv4(self: IPv6) ?IPv4 { | |
| 286 | if (!self.mapsToIPv4()) return null; | |
| 287 | return IPv4{ .octets = self.octets[12..][0..4].* }; | |
| 288 | } | |
| 289 | ||
| 290 | /// Returns true if the address is a multicast IPv6 address. | |
| 291 | pub fn isMulticast(self: IPv6) bool { | |
| 292 | return self.octets[0] == 0xFF; | |
| 293 | } | |
| 294 | ||
| 295 | /// Returns true if the address is a unicast link local IPv6 address. | |
| 296 | pub fn isLinkLocal(self: IPv6) bool { | |
| 297 | return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80; | |
| 298 | } | |
| 299 | ||
| 300 | /// Returns true if the address is a deprecated unicast site local | |
| 301 | /// IPv6 address. Refer to IETF RFC 3879 for more details as to | |
| 302 | /// why they are deprecated. | |
| 303 | pub fn isSiteLocal(self: IPv6) bool { | |
| 304 | return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0; | |
| 305 | } | |
| 306 | ||
| 307 | /// IPv6 multicast address scopes. | |
| 308 | pub const Scope = enum(u8) { | |
| 309 | interface = 1, | |
| 310 | link = 2, | |
| 311 | realm = 3, | |
| 312 | admin = 4, | |
| 313 | site = 5, | |
| 314 | organization = 8, | |
| 315 | global = 14, | |
| 316 | unknown = 0xFF, | |
| 317 | }; | |
| 318 | ||
| 319 | /// Returns the multicast scope of the address. | |
| 320 | pub fn scope(self: IPv6) Scope { | |
| 321 | if (!self.isMulticast()) return .unknown; | |
| 322 | ||
| 323 | return switch (self.octets[0] & 0x0F) { | |
| 324 | 1 => .interface, | |
| 325 | 2 => .link, | |
| 326 | 3 => .realm, | |
| 327 | 4 => .admin, | |
| 328 | 5 => .site, | |
| 329 | 8 => .organization, | |
| 330 | 14 => .global, | |
| 331 | else => .unknown, | |
| 332 | }; | |
| 333 | } | |
| 334 | ||
| 335 | /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the | |
| 336 | /// address lower-cased octets, while specifying 'X' or 'S' formats the | |
| 337 | /// address using upper-cased ASCII octets. | |
| 338 | /// | |
| 339 | /// The default specifier is 'x'. | |
| 340 | pub fn format( | |
| 341 | self: IPv6, | |
| 342 | comptime layout: []const u8, | |
| 343 | opts: fmt.FormatOptions, | |
| 344 | writer: anytype, | |
| 345 | ) !void { | |
| 346 | comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) { | |
| 347 | 'x', 'X' => |specifier| specifier, | |
| 348 | 's' => 'x', | |
| 349 | 'S' => 'X', | |
| 350 | else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."), | |
| 351 | }}; | |
| 352 | ||
| 353 | if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) { | |
| 354 | return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{ | |
| 355 | 0xFF, | |
| 356 | 0xFF, | |
| 357 | self.octets[12], | |
| 358 | self.octets[13], | |
| 359 | self.octets[14], | |
| 360 | self.octets[15], | |
| 361 | }); | |
| 362 | } | |
| 363 | ||
| 364 | const zero_span = span: { | |
| 365 | var i: usize = 0; | |
| 366 | while (i < self.octets.len) : (i += 2) { | |
| 367 | if (self.octets[i] == 0 and self.octets[i + 1] == 0) break; | |
| 368 | } else break :span .{ .from = 0, .to = 0 }; | |
| 369 | ||
| 370 | const from = i; | |
| 371 | ||
| 372 | while (i < self.octets.len) : (i += 2) { | |
| 373 | if (self.octets[i] != 0 or self.octets[i + 1] != 0) break; | |
| 374 | } | |
| 375 | ||
| 376 | break :span .{ .from = from, .to = i }; | |
| 377 | }; | |
| 378 | ||
| 379 | var i: usize = 0; | |
| 380 | while (i != 16) : (i += 2) { | |
| 381 | if (zero_span.from != zero_span.to and i == zero_span.from) { | |
| 382 | try writer.writeAll("::"); | |
| 383 | } else if (i >= zero_span.from and i < zero_span.to) {} else { | |
| 384 | if (i != 0 and i != zero_span.to) try writer.writeAll(":"); | |
| 385 | ||
| 386 | const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1]; | |
| 387 | try fmt.formatIntValue(val, specifier, .{}, writer); | |
| 388 | } | |
| 389 | } | |
| 390 | ||
| 391 | if (self.scope_id != no_scope_id and self.scope_id != 0) { | |
| 392 | try fmt.format(writer, "%{d}", .{self.scope_id}); | |
| 393 | } | |
| 394 | } | |
| 395 | ||
| 396 | /// Set of possible errors that may encountered when parsing an IPv6 | |
| 397 | /// address. | |
| 398 | pub const ParseError = error{ | |
| 399 | MalformedV4Mapping, | |
| 400 | BadScopeID, | |
| 401 | } || IPv4.ParseError; | |
| 402 | ||
| 403 | /// Parses an arbitrary IPv6 address, including link-local addresses. | |
| 404 | pub fn parse(buf: []const u8) ParseError!IPv6 { | |
| 405 | if (mem.lastIndexOfScalar(u8, buf, '%')) |index| { | |
| 406 | const ip_slice = buf[0..index]; | |
| 407 | const scope_id_slice = buf[index + 1 ..]; | |
| 408 | ||
| 409 | if (scope_id_slice.len == 0) return error.BadScopeID; | |
| 410 | ||
| 411 | const scope_id: u32 = switch (scope_id_slice[0]) { | |
| 412 | '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10), | |
| 413 | else => resolveScopeID(scope_id_slice), | |
| 414 | } catch return error.BadScopeID; | |
| 415 | ||
| 416 | return parseWithScopeID(ip_slice, scope_id); | |
| 417 | } | |
| 418 | ||
| 419 | return parseWithScopeID(buf, no_scope_id); | |
| 420 | } | |
| 421 | ||
| 422 | /// Parses an IPv6 address with a pre-specified scope ID. Presumes | |
| 423 | /// that the address is not a link-local address. | |
| 424 | pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 { | |
| 425 | var octets: [16]u8 = undefined; | |
| 426 | var octet: u16 = 0; | |
| 427 | var tail: [16]u8 = undefined; | |
| 428 | ||
| 429 | var out: []u8 = &octets; | |
| 430 | var index: u8 = 0; | |
| 431 | ||
| 432 | var saw_any_digits: bool = false; | |
| 433 | var abbrv: bool = false; | |
| 434 | ||
| 435 | for (buf) |c, i| { | |
| 436 | switch (c) { | |
| 437 | ':' => { | |
| 438 | if (!saw_any_digits) { | |
| 439 | if (abbrv) return error.UnexpectedToken; | |
| 440 | if (i != 0) abbrv = true; | |
| 441 | mem.set(u8, out[index..], 0); | |
| 442 | out = &tail; | |
| 443 | index = 0; | |
| 444 | continue; | |
| 445 | } | |
| 446 | if (index == 14) return error.TooManyOctets; | |
| 447 | ||
| 448 | out[index] = @truncate(u8, octet >> 8); | |
| 449 | index += 1; | |
| 450 | out[index] = @truncate(u8, octet); | |
| 451 | index += 1; | |
| 452 | ||
| 453 | octet = 0; | |
| 454 | saw_any_digits = false; | |
| 455 | }, | |
| 456 | '.' => { | |
| 457 | if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) { | |
| 458 | return error.MalformedV4Mapping; | |
| 459 | } | |
| 460 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; | |
| 461 | const v4 = try IPv4.parse(buf[start_index..]); | |
| 462 | octets[10] = 0xFF; | |
| 463 | octets[11] = 0xFF; | |
| 464 | mem.copy(u8, octets[12..], &v4.octets); | |
| 465 | ||
| 466 | return IPv6{ .octets = octets, .scope_id = scope_id }; | |
| 467 | }, | |
| 468 | else => { | |
| 469 | saw_any_digits = true; | |
| 470 | const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken; | |
| 471 | octet = math.mul(u16, octet, 16) catch return error.OctetOverflow; | |
| 472 | octet = math.add(u16, octet, digit) catch return error.OctetOverflow; | |
| 473 | }, | |
| 474 | } | |
| 475 | } | |
| 476 | ||
| 477 | if (!saw_any_digits and !abbrv) { | |
| 478 | return error.IncompleteAddress; | |
| 479 | } | |
| 480 | ||
| 481 | if (index == 14) { | |
| 482 | out[14] = @truncate(u8, octet >> 8); | |
| 483 | out[15] = @truncate(u8, octet); | |
| 484 | } else { | |
| 485 | out[index] = @truncate(u8, octet >> 8); | |
| 486 | index += 1; | |
| 487 | out[index] = @truncate(u8, octet); | |
| 488 | index += 1; | |
| 489 | mem.copy(u8, octets[16 - index ..], out[0..index]); | |
| 490 | } | |
| 491 | ||
| 492 | return IPv6{ .octets = octets, .scope_id = scope_id }; | |
| 493 | } | |
| 494 | }; | |
| 495 | ||
| 496 | test { | |
| 497 | testing.refAllDecls(@This()); | |
| 498 | } | |
| 499 | ||
| 500 | test "ip: convert to and from ipv6" { | |
| 501 | try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()}); | |
| 502 | testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4()); | |
| 503 | ||
| 504 | try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()}); | |
| 505 | testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4()); | |
| 506 | ||
| 507 | testing.expect(IPv4.localhost.toIPv6().toIPv4() == null); | |
| 508 | try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()}); | |
| 509 | } | |
| 510 | ||
| 511 | test "ipv4: parse & format" { | |
| 512 | const cases = [_][]const u8{ | |
| 513 | "0.0.0.0", | |
| 514 | "255.255.255.255", | |
| 515 | "1.2.3.4", | |
| 516 | "123.255.0.91", | |
| 517 | "127.0.0.1", | |
| 518 | }; | |
| 519 | ||
| 520 | for (cases) |case| { | |
| 521 | try testing.expectFmt(case, "{}", .{try IPv4.parse(case)}); | |
| 522 | } | |
| 523 | } | |
| 524 | ||
| 525 | test "ipv6: parse & format" { | |
| 526 | const inputs = [_][]const u8{ | |
| 527 | "FF01:0:0:0:0:0:0:FB", | |
| 528 | "FF01::Fb", | |
| 529 | "::1", | |
| 530 | "::", | |
| 531 | "2001:db8::", | |
| 532 | "::1234:5678", | |
| 533 | "2001:db8::1234:5678", | |
| 534 | "::ffff:123.5.123.5", | |
| 535 | }; | |
| 536 | ||
| 537 | const outputs = [_][]const u8{ | |
| 538 | "ff01::fb", | |
| 539 | "ff01::fb", | |
| 540 | "::1", | |
| 541 | "::", | |
| 542 | "2001:db8::", | |
| 543 | "::1234:5678", | |
| 544 | "2001:db8::1234:5678", | |
| 545 | "::ffff:123.5.123.5", | |
| 546 | }; | |
| 547 | ||
| 548 | for (inputs) |input, i| { | |
| 549 | try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); | |
| 550 | } | |
| 551 | } | |
| 552 | ||
| 553 | test "ipv6: parse & format addresses with scope ids" { | |
| 554 | if (!@hasDecl(os, "IFNAMESIZE")) return error.SkipZigTest; | |
| 555 | ||
| 556 | const inputs = [_][]const u8{ | |
| 557 | "FF01::FB%lo", | |
| 558 | }; | |
| 559 | ||
| 560 | const outputs = [_][]const u8{ | |
| 561 | "ff01::fb%1", | |
| 562 | }; | |
| 563 | ||
| 564 | for (inputs) |input, i| { | |
| 565 | try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); | |
| 566 | } | |
| 567 | } |
lib/std/x/os/os.zig deleted-9| ... | ... | @@ -1,9 +0,0 @@ |
| 1 | const std = @import("../../std.zig"); | |
| 2 | ||
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | pub const Socket = @import("Socket.zig"); | |
| 6 | ||
| 7 | test { | |
| 8 | testing.refAllDecls(@This()); | |
| 9 | } |