| 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,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006; |
| 832 | pub const SO_ERROR = 0x1007; | 832 | pub const SO_ERROR = 0x1007; |
| 833 | pub const SO_TYPE = 0x1008; | 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 | fn wstatus(x: u32) u32 { | 842 | fn wstatus(x: u32) u32 { |
| 836 | return x & 0o177; | 843 | return x & 0o177; |
| 837 | } | 844 | } |
lib/std/x.zig+8-1| ... | @@ -1 +1,8 @@ | ... | @@ -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,19 +2,11 @@ const std = @import("../../std.zig"); |
| 2 | 2 | ||
| 3 | const os = std.os; | 3 | const os = std.os; |
| 4 | const mem = std.mem; | 4 | const mem = std.mem; |
| 5 | const net = std.net; | ||
| 6 | const time = std.time; | 5 | const time = std.time; |
| 7 | const builtin = std.builtin; | ||
| 8 | const testing = std.testing; | ||
| 9 | 6 | ||
| 7 | /// A generic socket abstraction. | ||
| 10 | const Socket = @This(); | 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 | /// The underlying handle of a socket. | 10 | /// The underlying handle of a socket. |
| 19 | fd: os.socket_t, | 11 | fd: os.socket_t, |
| 20 | 12 | ||
| ... | @@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { | ... | @@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { |
| 23 | return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; | 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 | /// Closes the socket. | 23 | /// Closes the socket. |
| 27 | pub fn deinit(self: Socket) void { | 24 | pub fn deinit(self: Socket) void { |
| 28 | os.closeSocket(self.fd); | 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 | pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { | 29 | pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { |
| 33 | return os.shutdown(self.fd, how); | 30 | return os.shutdown(self.fd, how); |
| 34 | } | 31 | } |
| 35 | 32 | ||
| 36 | /// Binds the socket to an address. | 33 | /// Binds the socket to an address. |
| 37 | pub fn bind(self: Socket, address: net.Address) !void { | 34 | pub fn bind(self: Socket, comptime Address: type, address: Address) !void { |
| 38 | return os.bind(self.fd, &address.any, address.getOsSockLen()); | 35 | return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); |
| 39 | } | 36 | } |
| 40 | 37 | ||
| 41 | /// Start listening for incoming connections on the socket. | 38 | /// Start listening for incoming connections on the socket. |
| ... | @@ -44,22 +41,19 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void { | ... | @@ -44,22 +41,19 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void { |
| 44 | } | 41 | } |
| 45 | 42 | ||
| 46 | /// Have the socket attempt to the connect to an address. | 43 | /// Have the socket attempt to the connect to an address. |
| 47 | pub fn connect(self: Socket, address: net.Address) !void { | 44 | pub fn connect(self: Socket, comptime Address: type, address: Address) !void { |
| 48 | return os.connect(self.fd, &address.any, address.getOsSockLen()); | 45 | return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); |
| 49 | } | 46 | } |
| 50 | 47 | ||
| 51 | /// Accept a pending incoming connection queued to the kernel backlog | 48 | /// Accept a pending incoming connection queued to the kernel backlog |
| 52 | /// of the socket. | 49 | /// of the socket. |
| 53 | pub fn accept(self: Socket, flags: u32) !Socket.Connection { | 50 | pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection { |
| 54 | var address: os.sockaddr = undefined; | 51 | var address: Address = undefined; |
| 55 | var address_len: u32 = @sizeOf(os.sockaddr); | 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{ | 56 | return Connection.from(.{ .fd = fd }, address); |
| 60 | .socket = Socket{ .fd = fd }, | ||
| 61 | .address = net.Address.initPosix(@alignCast(4, &address)), | ||
| 62 | }; | ||
| 63 | } | 57 | } |
| 64 | 58 | ||
| 65 | /// Read data from the socket into the buffer provided. It returns the | 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,11 +94,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize { |
| 100 | } | 94 | } |
| 101 | 95 | ||
| 102 | /// Query the address that the socket is locally bounded to. | 96 | /// Query the address that the socket is locally bounded to. |
| 103 | pub fn getLocalAddress(self: Socket) !net.Address { | 97 | pub fn getLocalAddress(self: Socket, comptime Address: type) !Address { |
| 104 | var address: os.sockaddr = undefined; | 98 | var address: Address = undefined; |
| 105 | var address_len: u32 = @sizeOf(os.sockaddr); | 99 | var address_len: u32 = @sizeOf(Address); |
| 106 | try os.getsockname(self.fd, &address, &address_len); | 100 | try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len); |
| 107 | return net.Address.initPosix(@alignCast(4, &address)); | 101 | return address; |
| 108 | } | 102 | } |
| 109 | 103 | ||
| 110 | /// Query and return the latest cached error on the socket. | 104 | /// Query and return the latest cached error on the socket. |
| ... | @@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void { | ... | @@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void { |
| 164 | return error.UnsupportedSocketOption; | 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 | /// Set the write buffer size of the socket. | 161 | /// Set the write buffer size of the socket. |
| 195 | pub fn setWriteBufferSize(self: Socket, size: u32) !void { | 162 | pub fn setWriteBufferSize(self: Socket, size: u32) !void { |
| 196 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); | 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,52 +192,3 @@ pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { |
| 225 | 192 | ||
| 226 | return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); | 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 | } | ||