| author | |
| committer | |
| log | 2ab588049e41a96337a4fa8c2d9507320bc4278b |
| tree | d4958fc71559cdd4f4c3bd5952e266bfe1dd4499 |
| parent | c47028cd025f4ce405a9395fa979ff14a3e6349a |
The `Socket` abstraction was refactored to only comprise of methods that
can be generically used/applied to all socket domains and protocols.
A more comprehensive IPv4/IPv6 module derived from @LemonBoy's earlier
work was implemented under `std.x.os.IPv4` and `std.x.os.IPv6`. Using
this module, one can then combine them together into a union for example
in order to optimize memory usage when dealing with socket addresses.
A `TCP.Client` and `TCP.Listener` abstraction is introduced that is one
layer over the `Socket` abstraction, which isolates methods that can
only be applied to a "client socket" and a "listening socket". All prior
tests from the `Socket` abstraction, which all previously operated
assuming the socket is operating via. TCP/IP, were moved. All TCP socket
options were also moved into the `TCP.Client` and `TCP.Listener`
abstractions respectively away from the `Socket` abstraction.
Some additional socket options from @LemonBoy's prior PR for Darwin were
also moved in (i.e. SIGNOPIPE).6 files changed, 967 insertions(+), 113 deletions(-)
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/x.zig+8-1| ... | ... | @@ -1 +1,8 @@ |
| 1 | pub const os = @import("x/os/os.zig"); | |
| 1 | pub const os = struct { | |
| 2 | pub const Socket = @import("x/os/Socket.zig"); | |
| 3 | pub usingnamespace @import("x/os/net.zig"); | |
| 4 | }; | |
| 5 | ||
| 6 | pub const net = struct { | |
| 7 | pub const TCP = @import("x/net/TCP.zig"); | |
| 8 | }; |
lib/std/x/net/TCP.zig created+399| ... | ... | @@ -0,0 +1,399 @@ |
| 1 | const std = @import("../../std.zig"); | |
| 2 | ||
| 3 | const os = std.os; | |
| 4 | const fmt = std.fmt; | |
| 5 | const mem = std.mem; | |
| 6 | const testing = std.testing; | |
| 7 | ||
| 8 | const IPv4 = std.x.os.IPv4; | |
| 9 | const IPv6 = std.x.os.IPv6; | |
| 10 | const Socket = std.x.os.Socket; | |
| 11 | ||
| 12 | /// A generic TCP socket abstraction. | |
| 13 | const TCP = @This(); | |
| 14 | ||
| 15 | /// A TCP client-address pair. | |
| 16 | pub const Connection = struct { | |
| 17 | client: TCP.Client, | |
| 18 | address: TCP.Address, | |
| 19 | ||
| 20 | /// Enclose a TCP client and address into a client-address pair. | |
| 21 | pub fn from(socket: Socket, address: TCP.Address) Connection { | |
| 22 | return .{ .client = TCP.Client.from(socket), .address = address }; | |
| 23 | } | |
| 24 | ||
| 25 | /// Closes the underlying client of the connection. | |
| 26 | pub fn deinit(self: TCP.Connection) void { | |
| 27 | self.client.deinit(); | |
| 28 | } | |
| 29 | }; | |
| 30 | ||
| 31 | /// Possible domains that a TCP client/listener may operate over. | |
| 32 | pub const Domain = extern enum(u16) { | |
| 33 | ip = os.AF_INET, | |
| 34 | ipv6 = os.AF_INET6, | |
| 35 | }; | |
| 36 | ||
| 37 | /// A TCP client. | |
| 38 | pub const Client = struct { | |
| 39 | socket: Socket, | |
| 40 | ||
| 41 | /// Opens a new client. | |
| 42 | pub fn init(domain: TCP.Domain, flags: u32) !Client { | |
| 43 | return Client{ | |
| 44 | .socket = try Socket.init( | |
| 45 | @enumToInt(domain), | |
| 46 | os.SOCK_STREAM | flags, | |
| 47 | os.IPPROTO_TCP, | |
| 48 | ), | |
| 49 | }; | |
| 50 | } | |
| 51 | ||
| 52 | /// Enclose a TCP client over an existing socket. | |
| 53 | pub fn from(socket: Socket) Client { | |
| 54 | return Client{ .socket = socket }; | |
| 55 | } | |
| 56 | ||
| 57 | /// Closes the client. | |
| 58 | pub fn deinit(self: Client) void { | |
| 59 | self.socket.deinit(); | |
| 60 | } | |
| 61 | ||
| 62 | /// Shutdown either the read side, write side, or all sides of the client's underlying socket. | |
| 63 | pub fn shutdown(self: Client, how: os.ShutdownHow) !void { | |
| 64 | return self.socket.shutdown(how); | |
| 65 | } | |
| 66 | ||
| 67 | /// Have the client attempt to the connect to an address. | |
| 68 | pub fn connect(self: Client, address: TCP.Address) !void { | |
| 69 | return self.socket.connect(TCP.Address, address); | |
| 70 | } | |
| 71 | ||
| 72 | /// Read data from the socket into the buffer provided. It returns the | |
| 73 | /// number of bytes read into the buffer provided. | |
| 74 | pub fn read(self: Client, buf: []u8) !usize { | |
| 75 | return self.socket.read(buf); | |
| 76 | } | |
| 77 | ||
| 78 | /// Read data from the socket into the buffer provided with a set of flags | |
| 79 | /// specified. It returns the number of bytes read into the buffer provided. | |
| 80 | pub fn recv(self: Client, buf: []u8, flags: u32) !usize { | |
| 81 | return self.socket.recv(buf, flags); | |
| 82 | } | |
| 83 | ||
| 84 | /// Write a buffer of data provided to the socket. It returns the number | |
| 85 | /// of bytes that are written to the socket. | |
| 86 | pub fn write(self: Client, buf: []const u8) !usize { | |
| 87 | return self.socket.write(buf); | |
| 88 | } | |
| 89 | ||
| 90 | /// Writes multiple I/O vectors to the socket. It returns the number | |
| 91 | /// of bytes that are written to the socket. | |
| 92 | pub fn writev(self: Client, buffers: []const os.iovec_const) !usize { | |
| 93 | return self.socket.writev(buffers); | |
| 94 | } | |
| 95 | ||
| 96 | /// Write a buffer of data provided to the socket with a set of flags specified. | |
| 97 | /// It returns the number of bytes that are written to the socket. | |
| 98 | pub fn send(self: Client, buf: []const u8, flags: u32) !usize { | |
| 99 | return self.socket.send(buf, flags); | |
| 100 | } | |
| 101 | ||
| 102 | /// Writes multiple I/O vectors with a prepended message header to the socket | |
| 103 | /// with a set of flags specified. It returns the number of bytes that are | |
| 104 | /// written to the socket. | |
| 105 | pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize { | |
| 106 | return self.socket.sendmsg(msg, flags); | |
| 107 | } | |
| 108 | ||
| 109 | /// Query and return the latest cached error on the client's underlying socket. | |
| 110 | pub fn getError(self: Client) !void { | |
| 111 | return self.socket.getError(); | |
| 112 | } | |
| 113 | ||
| 114 | /// Query the read buffer size of the client's underlying socket. | |
| 115 | pub fn getReadBufferSize(self: Client) !u32 { | |
| 116 | return self.socket.getReadBufferSize(); | |
| 117 | } | |
| 118 | ||
| 119 | /// Query the write buffer size of the client's underlying socket. | |
| 120 | pub fn getWriteBufferSize(self: Client) !u32 { | |
| 121 | return self.socket.getWriteBufferSize(); | |
| 122 | } | |
| 123 | ||
| 124 | /// Query the address that the client's socket is locally bounded to. | |
| 125 | pub fn getLocalAddress(self: Client) !TCP.Address { | |
| 126 | return self.socket.getLocalAddress(TCP.Address); | |
| 127 | } | |
| 128 | ||
| 129 | /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if | |
| 130 | /// the host does not support sockets disabling Nagle's algorithm. | |
| 131 | pub fn setNoDelay(self: Client, enabled: bool) !void { | |
| 132 | if (comptime @hasDecl(os, "TCP_NODELAY")) { | |
| 133 | const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled))); | |
| 134 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes); | |
| 135 | } | |
| 136 | return error.UnsupportedSocketOption; | |
| 137 | } | |
| 138 | ||
| 139 | /// Set the write buffer size of the socket. | |
| 140 | pub fn setWriteBufferSize(self: Client, size: u32) !void { | |
| 141 | return self.socket.setWriteBufferSize(size); | |
| 142 | } | |
| 143 | ||
| 144 | /// Set the read buffer size of the socket. | |
| 145 | pub fn setReadBufferSize(self: Client, size: u32) !void { | |
| 146 | return self.socket.setReadBufferSize(size); | |
| 147 | } | |
| 148 | ||
| 149 | /// Set a timeout on the socket that is to occur if no messages are successfully written | |
| 150 | /// to its bound destination after a specified number of milliseconds. A subsequent write | |
| 151 | /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. | |
| 152 | pub fn setWriteTimeout(self: Client, milliseconds: usize) !void { | |
| 153 | return self.socket.setWriteTimeout(milliseconds); | |
| 154 | } | |
| 155 | ||
| 156 | /// Set a timeout on the socket that is to occur if no messages are successfully read | |
| 157 | /// from its bound destination after a specified number of milliseconds. A subsequent | |
| 158 | /// read from the socket will thereafter return `error.WouldBlock` should the timeout be | |
| 159 | /// exceeded. | |
| 160 | pub fn setReadTimeout(self: Client, milliseconds: usize) !void { | |
| 161 | return self.socket.setReadTimeout(milliseconds); | |
| 162 | } | |
| 163 | }; | |
| 164 | ||
| 165 | /// A TCP listener. | |
| 166 | pub const Listener = struct { | |
| 167 | socket: Socket, | |
| 168 | ||
| 169 | /// Opens a new listener. | |
| 170 | pub fn init(domain: TCP.Domain, flags: u32) !Listener { | |
| 171 | return Listener{ | |
| 172 | .socket = try Socket.init( | |
| 173 | @enumToInt(domain), | |
| 174 | os.SOCK_STREAM | flags, | |
| 175 | os.IPPROTO_TCP, | |
| 176 | ), | |
| 177 | }; | |
| 178 | } | |
| 179 | ||
| 180 | /// Closes the listener. | |
| 181 | pub fn deinit(self: Listener) void { | |
| 182 | self.socket.deinit(); | |
| 183 | } | |
| 184 | ||
| 185 | /// Shuts down the underlying listener's socket. The next subsequent call, or | |
| 186 | /// a current pending call to accept() after shutdown is called will return | |
| 187 | /// an error. | |
| 188 | pub fn shutdown(self: Listener) !void { | |
| 189 | return self.socket.shutdown(.recv); | |
| 190 | } | |
| 191 | ||
| 192 | /// Binds the listener's socket to an address. | |
| 193 | pub fn bind(self: Listener, address: TCP.Address) !void { | |
| 194 | return self.socket.bind(TCP.Address, address); | |
| 195 | } | |
| 196 | ||
| 197 | /// Start listening for incoming connections. | |
| 198 | pub fn listen(self: Listener, max_backlog_size: u31) !void { | |
| 199 | return self.socket.listen(max_backlog_size); | |
| 200 | } | |
| 201 | ||
| 202 | /// Accept a pending incoming connection queued to the kernel backlog | |
| 203 | /// of the listener's socket. | |
| 204 | pub fn accept(self: Listener, flags: u32) !TCP.Connection { | |
| 205 | return self.socket.accept(TCP.Connection, TCP.Address, flags); | |
| 206 | } | |
| 207 | ||
| 208 | /// Query and return the latest cached error on the listener's underlying socket. | |
| 209 | pub fn getError(self: Client) !void { | |
| 210 | return self.socket.getError(); | |
| 211 | } | |
| 212 | ||
| 213 | /// Query the address that the listener's socket is locally bounded to. | |
| 214 | pub fn getLocalAddress(self: Listener) !TCP.Address { | |
| 215 | return self.socket.getLocalAddress(TCP.Address); | |
| 216 | } | |
| 217 | ||
| 218 | /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if | |
| 219 | /// the host does not support sockets listening the same address. | |
| 220 | pub fn setReuseAddress(self: Listener, enabled: bool) !void { | |
| 221 | return self.socket.setReuseAddress(enabled); | |
| 222 | } | |
| 223 | ||
| 224 | /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if | |
| 225 | /// the host does not supports sockets listening on the same port. | |
| 226 | pub fn setReusePort(self: Listener, enabled: bool) !void { | |
| 227 | return self.socket.setReusePort(enabled); | |
| 228 | } | |
| 229 | ||
| 230 | /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not | |
| 231 | /// support TCP Fast Open. | |
| 232 | pub fn setFastOpen(self: Listener, enabled: bool) !void { | |
| 233 | if (comptime @hasDecl(os, "TCP_FASTOPEN")) { | |
| 234 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 235 | } | |
| 236 | return error.UnsupportedSocketOption; | |
| 237 | } | |
| 238 | ||
| 239 | /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns | |
| 240 | /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. | |
| 241 | pub fn setQuickACK(self: Listener, enabled: bool) !void { | |
| 242 | if (comptime @hasDecl(os, "TCP_QUICKACK")) { | |
| 243 | return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); | |
| 244 | } | |
| 245 | return error.UnsupportedSocketOption; | |
| 246 | } | |
| 247 | ||
| 248 | /// Set a timeout on the listener that is to occur if no new incoming connections come in | |
| 249 | /// after a specified number of milliseconds. A subsequent accept call to the listener | |
| 250 | /// will thereafter return `error.WouldBlock` should the timeout be exceeded. | |
| 251 | pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void { | |
| 252 | return self.socket.setReadTimeout(milliseconds); | |
| 253 | } | |
| 254 | }; | |
| 255 | ||
| 256 | /// A TCP socket address designated by a host IP and port. A TCP socket | |
| 257 | /// address comprises of 28 bytes. It may freely be used in place of | |
| 258 | /// `sockaddr` when working with socket syscalls. | |
| 259 | /// | |
| 260 | /// It is not recommended to touch the fields of an `Address`, but to | |
| 261 | /// instead make use of its available accessor methods. | |
| 262 | pub const Address = extern struct { | |
| 263 | family: u16, | |
| 264 | port: u16, | |
| 265 | host: extern union { | |
| 266 | ipv4: extern struct { | |
| 267 | address: IPv4, | |
| 268 | }, | |
| 269 | ipv6: extern struct { | |
| 270 | flow_info: u32 = 0, | |
| 271 | address: IPv6, | |
| 272 | }, | |
| 273 | }, | |
| 274 | ||
| 275 | /// Instantiate a new TCP address with a IPv4 host and port. | |
| 276 | pub fn initIPv4(host: IPv4, port: u16) Address { | |
| 277 | return Address{ | |
| 278 | .family = os.AF_INET, | |
| 279 | .port = mem.nativeToBig(u16, port), | |
| 280 | .host = .{ | |
| 281 | .ipv4 = .{ | |
| 282 | .address = host, | |
| 283 | }, | |
| 284 | }, | |
| 285 | }; | |
| 286 | } | |
| 287 | ||
| 288 | /// Instantiate a new TCP address with a IPv6 host and port. | |
| 289 | pub fn initIPv6(host: IPv6, port: u16) Address { | |
| 290 | return Address{ | |
| 291 | .family = os.AF_INET6, | |
| 292 | .port = mem.nativeToBig(u16, port), | |
| 293 | .host = .{ | |
| 294 | .ipv6 = .{ | |
| 295 | .address = host, | |
| 296 | }, | |
| 297 | }, | |
| 298 | }; | |
| 299 | } | |
| 300 | ||
| 301 | /// Extract the host of the address. | |
| 302 | pub fn getHost(self: Address) union(enum) { v4: IPv4, v6: IPv6 } { | |
| 303 | return switch (self.family) { | |
| 304 | os.AF_INET => .{ .v4 = self.host.ipv4.address }, | |
| 305 | os.AF_INET6 => .{ .v6 = self.host.ipv6.address }, | |
| 306 | else => unreachable, | |
| 307 | }; | |
| 308 | } | |
| 309 | ||
| 310 | /// Extract the port of the address. | |
| 311 | pub fn getPort(self: Address) u16 { | |
| 312 | return mem.nativeToBig(u16, self.port); | |
| 313 | } | |
| 314 | ||
| 315 | /// Set the port of the address. | |
| 316 | pub fn setPort(self: *Address, port: u16) void { | |
| 317 | self.port = mem.nativeToBig(u16, port); | |
| 318 | } | |
| 319 | ||
| 320 | /// Implements the `std.fmt.format` API. | |
| 321 | pub fn format( | |
| 322 | self: Address, | |
| 323 | comptime layout: []const u8, | |
| 324 | opts: fmt.FormatOptions, | |
| 325 | writer: anytype, | |
| 326 | ) !void { | |
| 327 | switch (self.getHost()) { | |
| 328 | .v4 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }), | |
| 329 | .v6 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }), | |
| 330 | } | |
| 331 | } | |
| 332 | }; | |
| 333 | ||
| 334 | test { | |
| 335 | testing.refAllDecls(@This()); | |
| 336 | } | |
| 337 | ||
| 338 | test "tcp: create non-blocking pair" { | |
| 339 | const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); | |
| 340 | defer a.deinit(); | |
| 341 | ||
| 342 | try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); | |
| 343 | try a.listen(128); | |
| 344 | ||
| 345 | const binded_address = try a.getLocalAddress(); | |
| 346 | ||
| 347 | const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); | |
| 348 | defer b.deinit(); | |
| 349 | ||
| 350 | testing.expectError(error.WouldBlock, b.connect(binded_address)); | |
| 351 | try b.getError(); | |
| 352 | ||
| 353 | const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); | |
| 354 | defer ab.deinit(); | |
| 355 | } | |
| 356 | ||
| 357 | test "tcp/client: set read timeout of 1 millisecond on blocking client" { | |
| 358 | const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); | |
| 359 | defer a.deinit(); | |
| 360 | ||
| 361 | try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); | |
| 362 | try a.listen(128); | |
| 363 | ||
| 364 | const binded_address = try a.getLocalAddress(); | |
| 365 | ||
| 366 | const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC); | |
| 367 | defer b.deinit(); | |
| 368 | ||
| 369 | try b.connect(binded_address); | |
| 370 | try b.setReadTimeout(1); | |
| 371 | ||
| 372 | const ab = try a.accept(os.SOCK_CLOEXEC); | |
| 373 | defer ab.deinit(); | |
| 374 | ||
| 375 | var buf: [1]u8 = undefined; | |
| 376 | testing.expectError(error.WouldBlock, b.read(&buf)); | |
| 377 | } | |
| 378 | ||
| 379 | test "tcp/listener: bind to unspecified ipv4 address" { | |
| 380 | const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); | |
| 381 | defer socket.deinit(); | |
| 382 | ||
| 383 | try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); | |
| 384 | try socket.listen(128); | |
| 385 | ||
| 386 | const address = try socket.getLocalAddress(); | |
| 387 | testing.expect(address.getHost() == .v4); | |
| 388 | } | |
| 389 | ||
| 390 | test "tcp/listener: bind to unspecified ipv6 address" { | |
| 391 | const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC); | |
| 392 | defer socket.deinit(); | |
| 393 | ||
| 394 | try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0)); | |
| 395 | try socket.listen(128); | |
| 396 | ||
| 397 | const address = try socket.getLocalAddress(); | |
| 398 | testing.expect(address.getHost() == .v6); | |
| 399 | } |
lib/std/x/os/Socket.zig+21-103| ... | ... | @@ -2,19 +2,11 @@ const std = @import("../../std.zig"); |
| 2 | 2 | |
| 3 | 3 | const os = std.os; |
| 4 | 4 | const mem = std.mem; |
| 5 | const net = std.net; | |
| 6 | 5 | const time = std.time; |
| 7 | const builtin = std.builtin; | |
| 8 | const testing = std.testing; | |
| 9 | 6 | |
| 7 | /// A generic socket abstraction. | |
| 10 | 8 | const Socket = @This(); |
| 11 | 9 | |
| 12 | /// A socket-address pair. | |
| 13 | pub const Connection = struct { | |
| 14 | socket: Socket, | |
| 15 | address: net.Address, | |
| 16 | }; | |
| 17 | ||
| 18 | 10 | /// The underlying handle of a socket. |
| 19 | 11 | fd: os.socket_t, |
| 20 | 12 | |
| ... | ... | @@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { |
| 23 | 15 | return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; |
| 24 | 16 | } |
| 25 | 17 | |
| 18 | /// Enclose a socket abstraction over an existing socket file descriptor. | |
| 19 | pub fn from(fd: os.socket_t) Socket { | |
| 20 | return Socket{ .fd = fd }; | |
| 21 | } | |
| 22 | ||
| 26 | 23 | /// Closes the socket. |
| 27 | 24 | pub fn deinit(self: Socket) void { |
| 28 | 25 | os.closeSocket(self.fd); |
| 29 | 26 | } |
| 30 | 27 | |
| 31 | /// Shutdown either the read side, or write side, or the entirety of a socket. | |
| 28 | /// Shutdown either the read side, write side, or all side of the socket. | |
| 32 | 29 | pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { |
| 33 | 30 | return os.shutdown(self.fd, how); |
| 34 | 31 | } |
| 35 | 32 | |
| 36 | 33 | /// 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()); | |
| 34 | pub fn bind(self: Socket, comptime Address: type, address: Address) !void { | |
| 35 | return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); | |
| 39 | 36 | } |
| 40 | 37 | |
| 41 | 38 | /// Start listening for incoming connections on the socket. |
| ... | ... | @@ -44,22 +41,19 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void { |
| 44 | 41 | } |
| 45 | 42 | |
| 46 | 43 | /// 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()); | |
| 44 | pub fn connect(self: Socket, comptime Address: type, address: Address) !void { | |
| 45 | return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); | |
| 49 | 46 | } |
| 50 | 47 | |
| 51 | 48 | /// Accept a pending incoming connection queued to the kernel backlog |
| 52 | 49 | /// of the socket. |
| 53 | pub fn accept(self: Socket, flags: u32) !Socket.Connection { | |
| 54 | var address: os.sockaddr = undefined; | |
| 55 | var address_len: u32 = @sizeOf(os.sockaddr); | |
| 50 | pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection { | |
| 51 | var address: Address = undefined; | |
| 52 | var address_len: u32 = @sizeOf(Address); | |
| 56 | 53 | |
| 57 | const fd = try os.accept(self.fd, &address, &address_len, flags); | |
| 54 | const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags); | |
| 58 | 55 | |
| 59 | return Connection{ | |
| 60 | .socket = Socket{ .fd = fd }, | |
| 61 | .address = net.Address.initPosix(@alignCast(4, &address)), | |
| 62 | }; | |
| 56 | return Connection.from(.{ .fd = fd }, address); | |
| 63 | 57 | } |
| 64 | 58 | |
| 65 | 59 | /// Read data from the socket into the buffer provided. It returns the |
| ... | ... | @@ -100,11 +94,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize { |
| 100 | 94 | } |
| 101 | 95 | |
| 102 | 96 | /// Query the address that the socket is locally bounded to. |
| 103 | pub fn getLocalAddress(self: Socket) !net.Address { | |
| 104 | var address: os.sockaddr = undefined; | |
| 105 | var address_len: u32 = @sizeOf(os.sockaddr); | |
| 106 | try os.getsockname(self.fd, &address, &address_len); | |
| 107 | return net.Address.initPosix(@alignCast(4, &address)); | |
| 97 | pub fn getLocalAddress(self: Socket, comptime Address: type) !Address { | |
| 98 | var address: Address = undefined; | |
| 99 | var address_len: u32 = @sizeOf(Address); | |
| 100 | try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len); | |
| 101 | return address; | |
| 108 | 102 | } |
| 109 | 103 | |
| 110 | 104 | /// Query and return the latest cached error on the socket. |
| ... | ... | @@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void { |
| 164 | 158 | return error.UnsupportedSocketOption; |
| 165 | 159 | } |
| 166 | 160 | |
| 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 | 161 | /// Set the write buffer size of the socket. |
| 195 | 162 | pub fn setWriteBufferSize(self: Socket, size: u32) !void { |
| 196 | 163 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); |
| ... | ... | @@ -225,52 +192,3 @@ pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { |
| 225 | 192 | |
| 226 | 193 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); |
| 227 | 194 | } |
| 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+532| ... | ... | @@ -0,0 +1,532 @@ |
| 1 | const std = @import("../../std.zig"); | |
| 2 | ||
| 3 | const os = std.os; | |
| 4 | const fmt = std.fmt; | |
| 5 | const mem = std.mem; | |
| 6 | const math = std.math; | |
| 7 | const builtin = std.builtin; | |
| 8 | const testing = std.testing; | |
| 9 | ||
| 10 | /// Resolves a network interface name into a scope/zone ID. It returns | |
| 11 | /// an error if either resolution fails, or if the interface name is | |
| 12 | /// too long. | |
| 13 | pub fn resolveScopeID(name: []const u8) !u32 { | |
| 14 | if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong; | |
| 15 | ||
| 16 | const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0); | |
| 17 | defer os.closeSocket(fd); | |
| 18 | ||
| 19 | var f: os.ifreq = undefined; | |
| 20 | mem.copy(u8, &f.ifrn.name, name); | |
| 21 | f.ifrn.name[name.len] = 0; | |
| 22 | ||
| 23 | try os.ioctl_SIOCGIFINDEX(fd, &f); | |
| 24 | ||
| 25 | return @bitCast(u32, f.ifru.ivalue); | |
| 26 | } | |
| 27 | ||
| 28 | /// An IPv4 address comprised of 4 bytes. | |
| 29 | pub const IPv4 = extern struct { | |
| 30 | /// Octets of a IPv4 address designating the local host. | |
| 31 | pub const localhost_octets = [_]u8{ 127, 0, 0, 1 }; | |
| 32 | ||
| 33 | /// The IPv4 address of the local host. | |
| 34 | pub const localhost: IPv4 = .{ .octets = localhost_octets }; | |
| 35 | ||
| 36 | /// Octets of an unspecified IPv4 address. | |
| 37 | pub const unspecified_octets = [_]u8{0} ** 4; | |
| 38 | ||
| 39 | /// An unspecified IPv4 address. | |
| 40 | pub const unspecified: IPv4 = .{ .octets = unspecified_octets }; | |
| 41 | ||
| 42 | /// Octets of a broadcast IPv4 address. | |
| 43 | pub const broadcast_octets = [_]u8{255} ** 4; | |
| 44 | ||
| 45 | /// An IPv4 broadcast address. | |
| 46 | pub const broadcast: IPv4 = .{ .octets = broadcast_octets }; | |
| 47 | ||
| 48 | /// The prefix octet pattern of a link-local IPv4 address. | |
| 49 | pub const link_local_prefix = [_]u8{ 169, 254 }; | |
| 50 | ||
| 51 | /// The prefix octet patterns of IPv4 addresses intended for | |
| 52 | /// documentation. | |
| 53 | pub const documentation_prefixes = [_][]const u8{ | |
| 54 | &[_]u8{ 192, 0, 2 }, | |
| 55 | &[_]u8{ 198, 51, 100 }, | |
| 56 | &[_]u8{ 203, 0, 113 }, | |
| 57 | }; | |
| 58 | ||
| 59 | octets: [4]u8, | |
| 60 | ||
| 61 | /// Returns whether or not the two addresses are equal to, less than, or | |
| 62 | /// greater than each other. | |
| 63 | pub fn cmp(self: IPv4, other: IPv4) math.Order { | |
| 64 | return mem.order(u8, &self.octets, &other.octets); | |
| 65 | } | |
| 66 | ||
| 67 | /// Returns true if both addresses are semantically equivalent. | |
| 68 | pub fn eql(self: IPv4, other: IPv4) bool { | |
| 69 | return mem.eql(u8, &self.octets, &other.octets); | |
| 70 | } | |
| 71 | ||
| 72 | /// Returns true if the address is a loopback address. | |
| 73 | pub fn isLoopback(self: IPv4) bool { | |
| 74 | return self.octets[0] == 127; | |
| 75 | } | |
| 76 | ||
| 77 | /// Returns true if the address is an unspecified IPv4 address. | |
| 78 | pub fn isUnspecified(self: IPv4) bool { | |
| 79 | return mem.eql(u8, &self.octets, &unspecified_octets); | |
| 80 | } | |
| 81 | ||
| 82 | /// Returns true if the address is a private IPv4 address. | |
| 83 | pub fn isPrivate(self: IPv4) bool { | |
| 84 | return self.octets[0] == 10 or | |
| 85 | (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or | |
| 86 | (self.octets[0] == 192 and self.octets[1] == 168); | |
| 87 | } | |
| 88 | ||
| 89 | /// Returns true if the address is a link-local IPv4 address. | |
| 90 | pub fn isLinkLocal(self: IPv4) bool { | |
| 91 | return mem.startsWith(u8, &self.octets, &link_local_prefix); | |
| 92 | } | |
| 93 | ||
| 94 | /// Returns true if the address is a multicast IPv4 address. | |
| 95 | pub fn isMulticast(self: IPv4) bool { | |
| 96 | return self.octets[0] >= 224 and self.octets[0] <= 239; | |
| 97 | } | |
| 98 | ||
| 99 | /// Returns true if the address is a IPv4 broadcast address. | |
| 100 | pub fn isBroadcast(self: IPv4) bool { | |
| 101 | return mem.eql(u8, &self.octets, &broadcast_octets); | |
| 102 | } | |
| 103 | ||
| 104 | /// Returns true if the address is in a range designated for documentation. Refer | |
| 105 | /// to IETF RFC 5737 for more details. | |
| 106 | pub fn isDocumentation(self: IPv4) bool { | |
| 107 | inline for (documentation_prefixes) |prefix| { | |
| 108 | if (mem.startsWith(u8, &self.octets, prefix)) { | |
| 109 | return true; | |
| 110 | } | |
| 111 | } | |
| 112 | return false; | |
| 113 | } | |
| 114 | ||
| 115 | /// Implements the `std.fmt.format` API. | |
| 116 | pub fn format( | |
| 117 | self: IPv4, | |
| 118 | comptime layout: []const u8, | |
| 119 | opts: fmt.FormatOptions, | |
| 120 | writer: anytype, | |
| 121 | ) !void { | |
| 122 | if (comptime layout.len != 0 and layout[0] != 's') { | |
| 123 | @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'."); | |
| 124 | } | |
| 125 | ||
| 126 | try fmt.format(writer, "{}.{}.{}.{}", .{ | |
| 127 | self.octets[0], | |
| 128 | self.octets[1], | |
| 129 | self.octets[2], | |
| 130 | self.octets[3], | |
| 131 | }); | |
| 132 | } | |
| 133 | ||
| 134 | /// Set of possible errors that may encountered when parsing an IPv4 | |
| 135 | /// address. | |
| 136 | pub const ParseError = error{ | |
| 137 | UnexpectedEndOfOctet, | |
| 138 | TooManyOctets, | |
| 139 | OctetOverflow, | |
| 140 | UnexpectedToken, | |
| 141 | IncompleteAddress, | |
| 142 | }; | |
| 143 | ||
| 144 | /// Parses an arbitrary IPv4 address. | |
| 145 | pub fn parse(buf: []const u8) ParseError!IPv4 { | |
| 146 | var octets: [4]u8 = undefined; | |
| 147 | var octet: u8 = 0; | |
| 148 | ||
| 149 | var index: u8 = 0; | |
| 150 | var saw_any_digits: bool = false; | |
| 151 | ||
| 152 | for (buf) |c| { | |
| 153 | switch (c) { | |
| 154 | '.' => { | |
| 155 | if (!saw_any_digits) return error.UnexpectedEndOfOctet; | |
| 156 | if (index == 3) return error.TooManyOctets; | |
| 157 | octets[index] = octet; | |
| 158 | index += 1; | |
| 159 | octet = 0; | |
| 160 | saw_any_digits = false; | |
| 161 | }, | |
| 162 | '0'...'9' => { | |
| 163 | saw_any_digits = true; | |
| 164 | octet = math.mul(u8, octet, 10) catch return error.OctetOverflow; | |
| 165 | octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow; | |
| 166 | }, | |
| 167 | else => return error.UnexpectedToken, | |
| 168 | } | |
| 169 | } | |
| 170 | ||
| 171 | if (index == 3 and saw_any_digits) { | |
| 172 | octets[index] = octet; | |
| 173 | return IPv4{ .octets = octets }; | |
| 174 | } | |
| 175 | ||
| 176 | return error.IncompleteAddress; | |
| 177 | } | |
| 178 | ||
| 179 | /// Maps the address to its IPv6 equivalent. In most cases, you would | |
| 180 | /// want to map the address to its IPv6 equivalent rather than directly | |
| 181 | /// re-interpreting the address. | |
| 182 | pub fn mapToIPv6(self: IPv4) IPv6 { | |
| 183 | var octets: [16]u8 = undefined; | |
| 184 | mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix); | |
| 185 | mem.copy(u8, octets[12..], &self.octets); | |
| 186 | return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; | |
| 187 | } | |
| 188 | ||
| 189 | /// Directly re-interprets the address to its IPv6 equivalent. In most | |
| 190 | /// cases, you would want to map the address to its IPv6 equivalent rather | |
| 191 | /// than directly re-interpreting the address. | |
| 192 | pub fn toIPv6(self: IPv4) IPv6 { | |
| 193 | var octets: [16]u8 = undefined; | |
| 194 | mem.set(u8, octets[0..12], 0); | |
| 195 | mem.copy(u8, octets[12..], &self.octets); | |
| 196 | return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id }; | |
| 197 | } | |
| 198 | }; | |
| 199 | ||
| 200 | /// An IPv6 address comprised of 16 bytes for an address, and 4 bytes | |
| 201 | /// for a scope ID; cumulatively summing to 20 bytes in total. | |
| 202 | pub const IPv6 = extern struct { | |
| 203 | /// Octets of a IPv6 address designating the local host. | |
| 204 | pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; | |
| 205 | ||
| 206 | /// The IPv6 address of the local host. | |
| 207 | pub const localhost: IPv6 = .{ | |
| 208 | .octets = localhost_octets, | |
| 209 | .scope_id = no_scope_id, | |
| 210 | }; | |
| 211 | ||
| 212 | /// Octets of an unspecified IPv6 address. | |
| 213 | pub const unspecified_octets = [_]u8{0} ** 16; | |
| 214 | ||
| 215 | /// An unspecified IPv6 address. | |
| 216 | pub const unspecified: IPv6 = .{ | |
| 217 | .octets = unspecified_octets, | |
| 218 | .scope_id = no_scope_id, | |
| 219 | }; | |
| 220 | ||
| 221 | /// The prefix of a IPv6 address that is mapped to a IPv4 address. | |
| 222 | pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2; | |
| 223 | ||
| 224 | /// A marker value used to designate an IPv6 address with no | |
| 225 | /// associated scope ID. | |
| 226 | pub const no_scope_id = math.maxInt(u32); | |
| 227 | ||
| 228 | octets: [16]u8, | |
| 229 | scope_id: u32, | |
| 230 | ||
| 231 | /// Returns whether or not the two addresses are equal to, less than, or | |
| 232 | /// greater than each other. | |
| 233 | pub fn cmp(self: IPv6, other: IPv6) math.Order { | |
| 234 | return switch (mem.order(u8, self.octets, other.octets)) { | |
| 235 | .eq => math.order(self.scope_id, other.scope_id), | |
| 236 | else => |order| order, | |
| 237 | }; | |
| 238 | } | |
| 239 | ||
| 240 | /// Returns true if both addresses are semantically equivalent. | |
| 241 | pub fn eql(self: IPv6, other: IPv6) bool { | |
| 242 | return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets); | |
| 243 | } | |
| 244 | ||
| 245 | /// Returns true if the address is an unspecified IPv6 address. | |
| 246 | pub fn isUnspecified(self: IPv6) bool { | |
| 247 | return mem.eql(u8, &self.octets, &unspecified_octets); | |
| 248 | } | |
| 249 | ||
| 250 | /// Returns true if the address is a loopback address. | |
| 251 | pub fn isLoopback(self: IPv6) bool { | |
| 252 | return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and | |
| 253 | mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 }); | |
| 254 | } | |
| 255 | ||
| 256 | /// Returns true if the address maps to an IPv4 address. | |
| 257 | pub fn mapsToIPv4(self: IPv6) bool { | |
| 258 | return mem.startsWith(u8, &self.octets, &v4_mapped_prefix); | |
| 259 | } | |
| 260 | ||
| 261 | /// Returns an IPv4 address representative of the address should | |
| 262 | /// it the address be mapped to an IPv4 address. It returns null | |
| 263 | /// otherwise. | |
| 264 | pub fn toIPv4(self: IPv6) ?IPv4 { | |
| 265 | if (!self.mapsToIPv4()) return null; | |
| 266 | return IPv4{ .octets = self.octets[12..][0..4].* }; | |
| 267 | } | |
| 268 | ||
| 269 | /// Returns true if the address is a multicast IPv6 address. | |
| 270 | pub fn isMulticast(self: IPv6) bool { | |
| 271 | return self.octets[0] == 0xFF; | |
| 272 | } | |
| 273 | ||
| 274 | /// Returns true if the address is a unicast link local IPv6 address. | |
| 275 | pub fn isLinkLocal(self: IPv6) bool { | |
| 276 | return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80; | |
| 277 | } | |
| 278 | ||
| 279 | /// Returns true if the address is a deprecated unicast site local | |
| 280 | /// IPv6 address. Refer to IETF RFC 3879 for more details as to | |
| 281 | /// why they are deprecated. | |
| 282 | pub fn isSiteLocal(self: IPv6) bool { | |
| 283 | return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0; | |
| 284 | } | |
| 285 | ||
| 286 | /// IPv6 multicast address scopes. | |
| 287 | pub const Scope = enum(u8) { | |
| 288 | interface = 1, | |
| 289 | link = 2, | |
| 290 | realm = 3, | |
| 291 | admin = 4, | |
| 292 | site = 5, | |
| 293 | organization = 8, | |
| 294 | global = 14, | |
| 295 | unknown = 0xFF, | |
| 296 | }; | |
| 297 | ||
| 298 | /// Returns the multicast scope of the address. | |
| 299 | pub fn scope(self: IPv6) Scope { | |
| 300 | if (!self.isMulticast()) return .unknown; | |
| 301 | ||
| 302 | return switch (self.octets[0] & 0x0F) { | |
| 303 | 1 => .interface, | |
| 304 | 2 => .link, | |
| 305 | 3 => .realm, | |
| 306 | 4 => .admin, | |
| 307 | 5 => .site, | |
| 308 | 8 => .organization, | |
| 309 | 14 => .global, | |
| 310 | else => .unknown, | |
| 311 | }; | |
| 312 | } | |
| 313 | ||
| 314 | /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the | |
| 315 | /// address lower-cased octets, while specifying 'X' or 'S' formats the | |
| 316 | /// address using upper-cased ASCII octets. | |
| 317 | /// | |
| 318 | /// The default specifier is 'x'. | |
| 319 | pub fn format( | |
| 320 | self: IPv6, | |
| 321 | comptime layout: []const u8, | |
| 322 | opts: fmt.FormatOptions, | |
| 323 | writer: anytype, | |
| 324 | ) !void { | |
| 325 | comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) { | |
| 326 | 'x', 'X' => |specifier| specifier, | |
| 327 | 's' => 'x', | |
| 328 | 'S' => 'X', | |
| 329 | else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."), | |
| 330 | }}; | |
| 331 | ||
| 332 | if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) { | |
| 333 | return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{ | |
| 334 | 0xFF, | |
| 335 | 0xFF, | |
| 336 | self.octets[12], | |
| 337 | self.octets[13], | |
| 338 | self.octets[14], | |
| 339 | self.octets[15], | |
| 340 | }); | |
| 341 | } | |
| 342 | ||
| 343 | const zero_span = span: { | |
| 344 | var i: usize = 0; | |
| 345 | while (i < self.octets.len) : (i += 2) { | |
| 346 | if (self.octets[i] == 0 and self.octets[i + 1] == 0) break; | |
| 347 | } else break :span .{ .from = 0, .to = 0 }; | |
| 348 | ||
| 349 | const from = i; | |
| 350 | ||
| 351 | while (i < self.octets.len) : (i += 2) { | |
| 352 | if (self.octets[i] != 0 or self.octets[i + 1] != 0) break; | |
| 353 | } | |
| 354 | ||
| 355 | break :span .{ .from = from, .to = i }; | |
| 356 | }; | |
| 357 | ||
| 358 | var i: usize = 0; | |
| 359 | while (i != 16) : (i += 2) { | |
| 360 | if (zero_span.from != zero_span.to and i == zero_span.from) { | |
| 361 | try writer.writeAll("::"); | |
| 362 | } else if (i >= zero_span.from and i < zero_span.to) {} else { | |
| 363 | if (i != 0 and i != zero_span.to) try writer.writeAll(":"); | |
| 364 | ||
| 365 | const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1]; | |
| 366 | try fmt.formatIntValue(val, specifier, .{}, writer); | |
| 367 | } | |
| 368 | } | |
| 369 | ||
| 370 | if (self.scope_id != no_scope_id and self.scope_id != 0) { | |
| 371 | try fmt.format(writer, "%{d}", .{self.scope_id}); | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | /// Set of possible errors that may encountered when parsing an IPv6 | |
| 376 | /// address. | |
| 377 | pub const ParseError = error{ | |
| 378 | MalformedV4Mapping, | |
| 379 | BadScopeID, | |
| 380 | } || IPv4.ParseError; | |
| 381 | ||
| 382 | /// Parses an arbitrary IPv6 address, including link-local addresses. | |
| 383 | pub fn parse(buf: []const u8) ParseError!IPv6 { | |
| 384 | if (mem.lastIndexOfScalar(u8, buf, '%')) |index| { | |
| 385 | const ip_slice = buf[0..index]; | |
| 386 | const scope_id_slice = buf[index + 1 ..]; | |
| 387 | ||
| 388 | if (scope_id_slice.len == 0) return error.BadScopeID; | |
| 389 | ||
| 390 | const scope_id: u32 = switch (scope_id_slice[0]) { | |
| 391 | '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10), | |
| 392 | else => resolveScopeID(scope_id_slice), | |
| 393 | } catch return error.BadScopeID; | |
| 394 | ||
| 395 | return parseWithScopeID(ip_slice, scope_id); | |
| 396 | } | |
| 397 | ||
| 398 | return parseWithScopeID(buf, no_scope_id); | |
| 399 | } | |
| 400 | ||
| 401 | /// Parses an IPv6 address with a pre-specified scope ID. Presumes | |
| 402 | /// that the address is not a link-local address. | |
| 403 | pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 { | |
| 404 | var octets: [16]u8 = undefined; | |
| 405 | var octet: u16 = 0; | |
| 406 | var tail: [16]u8 = undefined; | |
| 407 | ||
| 408 | var out: []u8 = &octets; | |
| 409 | var index: u8 = 0; | |
| 410 | ||
| 411 | var saw_any_digits: bool = false; | |
| 412 | var abbrv: bool = false; | |
| 413 | ||
| 414 | for (buf) |c, i| { | |
| 415 | switch (c) { | |
| 416 | ':' => { | |
| 417 | if (!saw_any_digits) { | |
| 418 | if (abbrv) return error.UnexpectedToken; | |
| 419 | if (i != 0) abbrv = true; | |
| 420 | mem.set(u8, out[index..], 0); | |
| 421 | out = &tail; | |
| 422 | index = 0; | |
| 423 | continue; | |
| 424 | } | |
| 425 | if (index == 14) return error.TooManyOctets; | |
| 426 | ||
| 427 | out[index] = @truncate(u8, octet >> 8); | |
| 428 | index += 1; | |
| 429 | out[index] = @truncate(u8, octet); | |
| 430 | index += 1; | |
| 431 | ||
| 432 | octet = 0; | |
| 433 | saw_any_digits = false; | |
| 434 | }, | |
| 435 | '.' => { | |
| 436 | if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) { | |
| 437 | return error.MalformedV4Mapping; | |
| 438 | } | |
| 439 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; | |
| 440 | const v4 = try IPv4.parse(buf[start_index..]); | |
| 441 | octets[10] = 0xFF; | |
| 442 | octets[11] = 0xFF; | |
| 443 | mem.copy(u8, octets[12..], &v4.octets); | |
| 444 | ||
| 445 | return IPv6{ .octets = octets, .scope_id = scope_id }; | |
| 446 | }, | |
| 447 | else => { | |
| 448 | saw_any_digits = true; | |
| 449 | const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken; | |
| 450 | octet = math.mul(u16, octet, 16) catch return error.OctetOverflow; | |
| 451 | octet = math.add(u16, octet, digit) catch return error.OctetOverflow; | |
| 452 | }, | |
| 453 | } | |
| 454 | } | |
| 455 | ||
| 456 | if (!saw_any_digits and !abbrv) { | |
| 457 | return error.IncompleteAddress; | |
| 458 | } | |
| 459 | ||
| 460 | if (index == 14) { | |
| 461 | out[14] = @truncate(u8, octet >> 8); | |
| 462 | out[15] = @truncate(u8, octet); | |
| 463 | } else { | |
| 464 | out[index] = @truncate(u8, octet >> 8); | |
| 465 | index += 1; | |
| 466 | out[index] = @truncate(u8, octet); | |
| 467 | index += 1; | |
| 468 | mem.copy(u8, octets[16 - index ..], out[0..index]); | |
| 469 | } | |
| 470 | ||
| 471 | return IPv6{ .octets = octets, .scope_id = scope_id }; | |
| 472 | } | |
| 473 | }; | |
| 474 | ||
| 475 | test { | |
| 476 | testing.refAllDecls(@This()); | |
| 477 | } | |
| 478 | ||
| 479 | test "ip: convert to and from ipv6" { | |
| 480 | try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()}); | |
| 481 | testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4()); | |
| 482 | ||
| 483 | try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()}); | |
| 484 | testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4()); | |
| 485 | ||
| 486 | testing.expect(IPv4.localhost.toIPv6().toIPv4() == null); | |
| 487 | try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()}); | |
| 488 | } | |
| 489 | ||
| 490 | test "ipv4: parse & format" { | |
| 491 | const cases = [_][]const u8{ | |
| 492 | "0.0.0.0", | |
| 493 | "255.255.255.255", | |
| 494 | "1.2.3.4", | |
| 495 | "123.255.0.91", | |
| 496 | "127.0.0.1", | |
| 497 | }; | |
| 498 | ||
| 499 | for (cases) |case| { | |
| 500 | try testing.expectFmt(case, "{}", .{try IPv4.parse(case)}); | |
| 501 | } | |
| 502 | } | |
| 503 | ||
| 504 | test "ipv6: parse & format" { | |
| 505 | const inputs = [_][]const u8{ | |
| 506 | "FF01:0:0:0:0:0:0:FB", | |
| 507 | "FF01::Fb", | |
| 508 | "::1", | |
| 509 | "::", | |
| 510 | "2001:db8::", | |
| 511 | "::1234:5678", | |
| 512 | "2001:db8::1234:5678", | |
| 513 | "::ffff:123.5.123.5", | |
| 514 | "FF01::FB%lo", | |
| 515 | }; | |
| 516 | ||
| 517 | const outputs = [_][]const u8{ | |
| 518 | "ff01::fb", | |
| 519 | "ff01::fb", | |
| 520 | "::1", | |
| 521 | "::", | |
| 522 | "2001:db8::", | |
| 523 | "::1234:5678", | |
| 524 | "2001:db8::1234:5678", | |
| 525 | "::ffff:123.5.123.5", | |
| 526 | "ff01::fb%1", | |
| 527 | }; | |
| 528 | ||
| 529 | for (inputs) |input, i| { | |
| 530 | try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); | |
| 531 | } | |
| 532 | } |
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 | } |