| ... | @@ -26,10 +26,12 @@ pub const IpAddress = union(enum) { | ... | @@ -26,10 +26,12 @@ pub const IpAddress = union(enum) { |
| 26 | pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; | 26 | pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; |
| 27 | | 27 | |
| 28 | /// Parse the given IP address string into an `IpAddress` value. | 28 | /// Parse the given IP address string into an `IpAddress` value. |
| | 29 | /// |
| | 30 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| | 31 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be |
| | 32 | /// called instead. |
| 29 | pub fn parse(name: []const u8, port: u16) !IpAddress { | 33 | pub fn parse(name: []const u8, port: u16) !IpAddress { |
| 30 | if (Ip4Address.parse(name, port)) |ip4| { | 34 | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { |
| 31 | return .{ .ip4 = ip4 }; | | |
| 32 | } else |err| switch (err) { | | |
| 33 | error.Overflow, | 35 | error.Overflow, |
| 34 | error.InvalidEnd, | 36 | error.InvalidEnd, |
| 35 | error.InvalidCharacter, | 37 | error.InvalidCharacter, |
| ... | @@ -38,26 +40,41 @@ pub const IpAddress = union(enum) { | ... | @@ -38,26 +40,41 @@ pub const IpAddress = union(enum) { |
| 38 | => {}, | 40 | => {}, |
| 39 | } | 41 | } |
| 40 | | 42 | |
| 41 | if (Ip6Address.parse(name, port)) |ip6| { | 43 | return parseIp6(name, port); |
| 42 | return .{ .ip6 = ip6 }; | 44 | } |
| 43 | } else |err| switch (err) { | 45 | |
| | 46 | pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { |
| | 47 | return .{ .ip4 = try Ip4Address.parse(text, port) }; |
| | 48 | } |
| | 49 | |
| | 50 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| | 51 | /// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be |
| | 52 | /// called instead. |
| | 53 | pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress { |
| | 54 | return .{ .ip6 = try Ip6Address.parse(text, port) }; |
| | 55 | } |
| | 56 | |
| | 57 | /// This function requires an `Io` parameter because it must query the operating |
| | 58 | /// system to convert interface name to index. For example, in |
| | 59 | /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by |
| | 60 | /// creating a socket and then using an `ioctl` syscall. |
| | 61 | /// |
| | 62 | /// For a pure function that cannot handle scopes, see `parse`. |
| | 63 | pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress { |
| | 64 | if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { |
| 44 | error.Overflow, | 65 | error.Overflow, |
| 45 | error.InvalidEnd, | 66 | error.InvalidEnd, |
| 46 | error.InvalidCharacter, | 67 | error.InvalidCharacter, |
| 47 | error.Incomplete, | 68 | error.Incomplete, |
| 48 | error.InvalidIpv4Mapping, | 69 | error.NonCanonical, |
| 49 | => {}, | 70 | => {}, |
| 50 | } | 71 | } |
| 51 | | 72 | |
| 52 | return error.InvalidIpAddressFormat; | 73 | return resolveIp6(io, text, port); |
| 53 | } | 74 | } |
| 54 | | 75 | |
| 55 | pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress { | 76 | pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress { |
| 56 | return .{ .ip6 = try Ip6Address.parse(buffer, port) }; | 77 | return .{ .ip6 = try Ip6Address.resolve(io, text, port) }; |
| 57 | } | | |
| 58 | | | |
| 59 | pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress { | | |
| 60 | return .{ .ip4 = try Ip4Address.parse(buffer, port) }; | | |
| 61 | } | 78 | } |
| 62 | | 79 | |
| 63 | /// Returns the port in native endian. | 80 | /// Returns the port in native endian. |
| ... | @@ -74,6 +91,19 @@ pub const IpAddress = union(enum) { | ... | @@ -74,6 +91,19 @@ pub const IpAddress = union(enum) { |
| 74 | } | 91 | } |
| 75 | } | 92 | } |
| 76 | | 93 | |
| | 94 | /// Includes the optional scope ("%foo" at the end) in IPv6 addresses. |
| | 95 | /// |
| | 96 | /// See `format` for an alternative that omits scopes and does |
| | 97 | /// not require an `Io` parameter. |
| | 98 | pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void { |
| | 99 | switch (a) { |
| | 100 | .ip4 => |x| return x.format(w), |
| | 101 | .ip6 => |x| return x.formatResolved(io, w), |
| | 102 | } |
| | 103 | } |
| | 104 | |
| | 105 | /// See `formatResolved` for an alternative that additionally prints the optional |
| | 106 | /// scope at the end of IPv6 addresses and requires an `Io` parameter. |
| 77 | pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void { | 107 | pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void { |
| 78 | switch (a) { | 108 | switch (a) { |
| 79 | inline .ip4, .ip6 => |x| return x.format(w), | 109 | inline .ip4, .ip6 => |x| return x.format(w), |
| ... | @@ -99,11 +129,12 @@ pub const IpAddress = union(enum) { | ... | @@ -99,11 +129,12 @@ pub const IpAddress = union(enum) { |
| 99 | } | 129 | } |
| 100 | }; | 130 | }; |
| 101 | | 131 | |
| | 132 | /// An IPv4 address in binary memory layout. |
| 102 | pub const Ip4Address = struct { | 133 | pub const Ip4Address = struct { |
| 103 | bytes: [4]u8, | 134 | bytes: [4]u8, |
| 104 | port: u16, | 135 | port: u16, |
| 105 | | 136 | |
| 106 | pub fn localhost(port: u16) Ip4Address { | 137 | pub fn loopback(port: u16) Ip4Address { |
| 107 | return .{ | 138 | return .{ |
| 108 | .bytes = .{ 127, 0, 0, 1 }, | 139 | .bytes = .{ 127, 0, 0, 1 }, |
| 109 | .port = port, | 140 | .port = port, |
| ... | @@ -162,21 +193,14 @@ pub const Ip4Address = struct { | ... | @@ -162,21 +193,14 @@ pub const Ip4Address = struct { |
| 162 | } | 193 | } |
| 163 | }; | 194 | }; |
| 164 | | 195 | |
| | 196 | /// An IPv6 address in binary memory layout. |
| 165 | pub const Ip6Address = struct { | 197 | pub const Ip6Address = struct { |
| 166 | /// Native endian | 198 | /// Native endian |
| 167 | port: u16, | 199 | port: u16, |
| 168 | /// Big endian | 200 | /// Big endian |
| 169 | bytes: [16]u8, | 201 | bytes: [16]u8, |
| 170 | flowinfo: u32 = 0, | 202 | flow: u32 = 0, |
| 171 | scope_id: u32 = 0, | 203 | interface: Interface = .none, |
| 172 | | | |
| 173 | pub const ParseError = error{ | | |
| 174 | Overflow, | | |
| 175 | InvalidCharacter, | | |
| 176 | InvalidEnd, | | |
| 177 | InvalidIpv4Mapping, | | |
| 178 | Incomplete, | | |
| 179 | }; | | |
| 180 | | 204 | |
| 181 | pub const Policy = struct { | 205 | pub const Policy = struct { |
| 182 | addr: [16]u8, | 206 | addr: [16]u8, |
| ... | @@ -186,192 +210,205 @@ pub const Ip6Address = struct { | ... | @@ -186,192 +210,205 @@ pub const Ip6Address = struct { |
| 186 | label: u8, | 210 | label: u8, |
| 187 | }; | 211 | }; |
| 188 | | 212 | |
| 189 | pub fn localhost(port: u16) Ip6Address { | 213 | pub fn loopback(port: u16) Ip6Address { |
| 190 | return .{ | 214 | return .{ |
| 191 | .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, | 215 | .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, |
| 192 | .port = port, | 216 | .port = port, |
| 193 | }; | 217 | }; |
| 194 | } | 218 | } |
| 195 | | 219 | |
| 196 | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { | 220 | /// An IPv6 address but with `Interface` as a name rather than index. |
| 197 | var result: Ip6Address = .{ | 221 | pub const Unresolved = struct { |
| 198 | .port = port, | 222 | /// Big endian |
| 199 | .bytes = undefined, | 223 | bytes: [16]u8, |
| | 224 | interface_name: ?Interface.Name, |
| | 225 | |
| | 226 | pub const Parsed = union(enum) { |
| | 227 | success: Unresolved, |
| | 228 | invalid_byte: usize, |
| | 229 | unexpected_end, |
| 200 | }; | 230 | }; |
| 201 | var ip_slice: *[16]u8 = &result.bytes; | | |
| 202 | | 231 | |
| 203 | var tail: [16]u8 = undefined; | 232 | pub fn parse(buffer: []const u8) Parsed { |
| | 233 | if (buffer.len < 2) return .unexpected_end; |
| | 234 | var parts: [8]u16 = @splat(0); |
| | 235 | var parts_i: usize = 0; |
| | 236 | var i: usize = 0; |
| | 237 | var digit_i: usize = 0; |
| | 238 | const State = union(enum) { digit, colon, end }; |
| | 239 | state: switch (State.digit) { |
| | 240 | .digit => c: switch (buffer[i]) { |
| | 241 | 'a'...'f' => |c| { |
| | 242 | const digit = c - 'a'; |
| | 243 | parts[parts_i] = parts[parts_i] * 16 + digit; |
| | 244 | if (digit_i == 3) { |
| | 245 | digit_i = 0; |
| | 246 | parts_i += 1; |
| | 247 | i += 1; |
| | 248 | if (parts.len - parts_i == 0) continue :state .end; |
| | 249 | continue :state .colon; |
| | 250 | } |
| | 251 | digit_i += 1; |
| | 252 | if (buffer.len - i == 0) return .unexpected_end; |
| | 253 | i += 1; |
| | 254 | continue :c buffer[i]; |
| | 255 | }, |
| | 256 | 'A'...'F' => |c| continue :c c + ('a' - 'A'), |
| | 257 | '0'...'9' => |c| continue :c c + ('a' - '0'), |
| | 258 | ':' => @panic("TODO"), |
| | 259 | else => return .{ .invalid_byte = i }, |
| | 260 | }, |
| | 261 | .colon => @panic("TODO"), |
| | 262 | .end => @panic("TODO"), |
| | 263 | } |
| | 264 | } |
| | 265 | |
| | 266 | pub const FromAddressError = Interface.NameError; |
| 204 | | 267 | |
| 205 | var x: u16 = 0; | 268 | pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved { |
| 206 | var saw_any_digits = false; | 269 | if (a.interface.isNone()) return .{ |
| 207 | var index: u8 = 0; | 270 | .bytes = a.bytes, |
| 208 | var scope_id = false; | 271 | .interface_name = null, |
| 209 | var abbrv = false; | 272 | }; |
| 210 | for (buffer, 0..) |c, i| { | 273 | return .{ |
| 211 | if (scope_id) { | 274 | .bytes = a.bytes, |
| 212 | if (c >= '0' and c <= '9') { | 275 | .interface_name = try a.interface.name(io), |
| 213 | const digit = c - '0'; | 276 | }; |
| 214 | { | 277 | } |
| 215 | const ov = @mulWithOverflow(result.scope_id, 10); | 278 | |
| 216 | if (ov[1] != 0) return error.Overflow; | 279 | pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { |
| 217 | result.scope_id = ov[0]; | 280 | const bytes = &u.bytes; |
| 218 | } | 281 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { |
| 219 | { | 282 | try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 220 | const ov = @addWithOverflow(result.scope_id, digit); | 283 | } else { |
| 221 | if (ov[1] != 0) return error.Overflow; | 284 | const parts: [8]u16 = .{ |
| 222 | result.scope_id = ov[0]; | 285 | std.mem.readInt(u16, bytes[0..2], .big), |
| | 286 | std.mem.readInt(u16, bytes[2..4], .big), |
| | 287 | std.mem.readInt(u16, bytes[4..6], .big), |
| | 288 | std.mem.readInt(u16, bytes[6..8], .big), |
| | 289 | std.mem.readInt(u16, bytes[8..10], .big), |
| | 290 | std.mem.readInt(u16, bytes[10..12], .big), |
| | 291 | std.mem.readInt(u16, bytes[12..14], .big), |
| | 292 | std.mem.readInt(u16, bytes[14..16], .big), |
| | 293 | }; |
| | 294 | |
| | 295 | // Find the longest zero run |
| | 296 | var longest_start: usize = 8; |
| | 297 | var longest_len: usize = 0; |
| | 298 | var current_start: usize = 0; |
| | 299 | var current_len: usize = 0; |
| | 300 | |
| | 301 | for (parts, 0..) |part, i| { |
| | 302 | if (part == 0) { |
| | 303 | if (current_len == 0) { |
| | 304 | current_start = i; |
| | 305 | } |
| | 306 | current_len += 1; |
| | 307 | if (current_len > longest_len) { |
| | 308 | longest_start = current_start; |
| | 309 | longest_len = current_len; |
| | 310 | } |
| | 311 | } else { |
| | 312 | current_len = 0; |
| 223 | } | 313 | } |
| 224 | } else { | | |
| 225 | return error.InvalidCharacter; | | |
| 226 | } | 314 | } |
| 227 | } else if (c == ':') { | | |
| 228 | if (!saw_any_digits) { | | |
| 229 | if (abbrv) return error.InvalidCharacter; // ':::' | | |
| 230 | if (i != 0) abbrv = true; | | |
| 231 | @memset(ip_slice[index..], 0); | | |
| 232 | ip_slice = tail[0..]; | | |
| 233 | index = 0; | | |
| 234 | continue; | | |
| 235 | } | | |
| 236 | if (index == 14) { | | |
| 237 | return error.InvalidEnd; | | |
| 238 | } | | |
| 239 | ip_slice[index] = @as(u8, @truncate(x >> 8)); | | |
| 240 | index += 1; | | |
| 241 | ip_slice[index] = @as(u8, @truncate(x)); | | |
| 242 | index += 1; | | |
| 243 | | 315 | |
| 244 | x = 0; | 316 | // Only compress if the longest zero run is 2 or more |
| 245 | saw_any_digits = false; | 317 | if (longest_len < 2) { |
| 246 | } else if (c == '%') { | 318 | longest_start = 8; |
| 247 | if (!saw_any_digits) { | 319 | longest_len = 0; |
| 248 | return error.InvalidCharacter; | | |
| 249 | } | | |
| 250 | scope_id = true; | | |
| 251 | saw_any_digits = false; | | |
| 252 | } else if (c == '.') { | | |
| 253 | if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) { | | |
| 254 | // must start with '::ffff:' | | |
| 255 | return error.InvalidIpv4Mapping; | | |
| 256 | } | 320 | } |
| 257 | const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1; | 321 | |
| 258 | const addr = (Ip4Address.parse(buffer[start_index..], 0) catch { | 322 | try w.writeAll("["); |
| 259 | return error.InvalidIpv4Mapping; | 323 | var i: usize = 0; |
| 260 | }).bytes; | 324 | var abbrv = false; |
| 261 | ip_slice = result.bytes[0..]; | 325 | while (i < parts.len) : (i += 1) { |
| 262 | ip_slice[10] = 0xff; | 326 | if (i == longest_start) { |
| 263 | ip_slice[11] = 0xff; | 327 | // Emit "::" for the longest zero run |
| 264 | | 328 | if (!abbrv) { |
| 265 | ip_slice[12] = addr[0]; | 329 | try w.writeAll(if (i == 0) "::" else ":"); |
| 266 | ip_slice[13] = addr[1]; | 330 | abbrv = true; |
| 267 | ip_slice[14] = addr[2]; | 331 | } |
| 268 | ip_slice[15] = addr[3]; | 332 | i += longest_len - 1; // Skip the compressed range |
| 269 | return result; | 333 | continue; |
| 270 | } else { | 334 | } |
| 271 | const digit = try std.fmt.charToDigit(c, 16); | 335 | if (abbrv) { |
| 272 | { | 336 | abbrv = false; |
| 273 | const ov = @mulWithOverflow(x, 16); | 337 | } |
| 274 | if (ov[1] != 0) return error.Overflow; | 338 | try w.print("{x}", .{parts[i]}); |
| 275 | x = ov[0]; | 339 | if (i != parts.len - 1) { |
| 276 | } | 340 | try w.writeAll(":"); |
| 277 | { | 341 | } |
| 278 | const ov = @addWithOverflow(x, digit); | | |
| 279 | if (ov[1] != 0) return error.Overflow; | | |
| 280 | x = ov[0]; | | |
| 281 | } | 342 | } |
| 282 | saw_any_digits = true; | | |
| 283 | } | 343 | } |
| | 344 | if (u.interface_name) |n| try w.print("%{s}", .{n.toSlice()}); |
| 284 | } | 345 | } |
| | 346 | }; |
| 285 | | 347 | |
| 286 | if (!saw_any_digits and !abbrv) { | 348 | pub const ParseError = error{ |
| 287 | return error.Incomplete; | 349 | /// If this is returned, more detailed diagnostics can be obtained by |
| 288 | } | 350 | /// calling `Ip6Address.Parsed.init`. |
| 289 | if (!abbrv and index < 14) { | 351 | ParseFailed, |
| 290 | return error.Incomplete; | 352 | /// If this is returned, the IPv6 address had a scope id on it ("%foo" |
| 291 | } | 353 | /// at the end) which requires calling `resolve`. |
| | 354 | UnresolvedScope, |
| | 355 | }; |
| 292 | | 356 | |
| 293 | if (index == 14) { | 357 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| 294 | ip_slice[14] = @as(u8, @truncate(x >> 8)); | 358 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be |
| 295 | ip_slice[15] = @as(u8, @truncate(x)); | 359 | /// called instead. |
| 296 | return result; | 360 | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { |
| 297 | } else { | 361 | switch (Unresolved.parse(buffer)) { |
| 298 | ip_slice[index] = @as(u8, @truncate(x >> 8)); | 362 | .success => |p| return .{ |
| 299 | index += 1; | 363 | .bytes = p.bytes, |
| 300 | ip_slice[index] = @as(u8, @truncate(x)); | 364 | .port = port, |
| 301 | index += 1; | 365 | .interface = if (p.interface_name != null) return error.UnresolvedScope else .none, |
| 302 | @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]); | 366 | }, |
| 303 | return result; | 367 | else => return error.ParseFailed, |
| 304 | } | 368 | } |
| | 369 | return .{ .ip6 = try Ip6Address.parse(buffer, port) }; |
| 305 | } | 370 | } |
| 306 | | 371 | |
| 307 | pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { | 372 | pub const ResolveError = error{ |
| 308 | const bytes = &a.bytes; | 373 | /// If this is returned, more detailed diagnostics can be obtained by |
| 309 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { | 374 | /// calling the `Parsed.init` function. |
| 310 | try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{ | 375 | ParseFailed, |
| 311 | bytes[12], bytes[13], bytes[14], bytes[15], a.port, | 376 | } || Interface.Name.ResolveError; |
| 312 | }); | 377 | |
| 313 | return; | 378 | /// This function requires an `Io` parameter because it must query the operating |
| 314 | } | 379 | /// system to convert interface name to index. For example, in |
| 315 | const parts: [8]u16 = .{ | 380 | /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by |
| 316 | std.mem.readInt(u16, bytes[0..2], .big), | 381 | /// creating a socket and then using an `ioctl` syscall. |
| 317 | std.mem.readInt(u16, bytes[2..4], .big), | 382 | pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address { |
| 318 | std.mem.readInt(u16, bytes[4..6], .big), | 383 | return switch (Unresolved.parse(buffer)) { |
| 319 | std.mem.readInt(u16, bytes[6..8], .big), | 384 | .success => |p| return .{ |
| 320 | std.mem.readInt(u16, bytes[8..10], .big), | 385 | .bytes = p.bytes, |
| 321 | std.mem.readInt(u16, bytes[10..12], .big), | 386 | .port = port, |
| 322 | std.mem.readInt(u16, bytes[12..14], .big), | 387 | .interface = if (p.interface_name) |n| try n.resolve(io) else .none, |
| 323 | std.mem.readInt(u16, bytes[14..16], .big), | 388 | }, |
| | 389 | else => return error.ParseFailed, |
| 324 | }; | 390 | }; |
| | 391 | } |
| 325 | | 392 | |
| 326 | // Find the longest zero run | 393 | pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError; |
| 327 | var longest_start: usize = 8; | | |
| 328 | var longest_len: usize = 0; | | |
| 329 | var current_start: usize = 0; | | |
| 330 | var current_len: usize = 0; | | |
| 331 | | 394 | |
| 332 | for (parts, 0..) |part, i| { | 395 | /// Includes the optional scope ("%foo" at the end). |
| 333 | if (part == 0) { | 396 | /// |
| 334 | if (current_len == 0) { | 397 | /// See `format` for an alternative that omits scopes and does |
| 335 | current_start = i; | 398 | /// not require an `Io` parameter. |
| 336 | } | 399 | pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void { |
| 337 | current_len += 1; | 400 | const u: Unresolved = try .fromAddress(io); |
| 338 | if (current_len > longest_len) { | 401 | try w.print("[{f}]:{d}", .{ u, a.port }); |
| 339 | longest_start = current_start; | 402 | } |
| 340 | longest_len = current_len; | | |
| 341 | } | | |
| 342 | } else { | | |
| 343 | current_len = 0; | | |
| 344 | } | | |
| 345 | } | | |
| 346 | | | |
| 347 | // Only compress if the longest zero run is 2 or more | | |
| 348 | if (longest_len < 2) { | | |
| 349 | longest_start = 8; | | |
| 350 | longest_len = 0; | | |
| 351 | } | | |
| 352 | | 403 | |
| 353 | try w.writeAll("["); | 404 | /// See `formatResolved` for an alternative that additionally prints the optional |
| 354 | var i: usize = 0; | 405 | /// scope at the end of addresses and requires an `Io` parameter. |
| 355 | var abbrv = false; | 406 | pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { |
| 356 | while (i < parts.len) : (i += 1) { | 407 | const u: Unresolved = .{ |
| 357 | if (i == longest_start) { | 408 | .bytes = a.bytes, |
| 358 | // Emit "::" for the longest zero run | 409 | .interface_name = null, |
| 359 | if (!abbrv) { | 410 | }; |
| 360 | try w.writeAll(if (i == 0) "::" else ":"); | 411 | try w.print("[{f}]:{d}", .{ u, a.port }); |
| 361 | abbrv = true; | | |
| 362 | } | | |
| 363 | i += longest_len - 1; // Skip the compressed range | | |
| 364 | continue; | | |
| 365 | } | | |
| 366 | if (abbrv) { | | |
| 367 | abbrv = false; | | |
| 368 | } | | |
| 369 | try w.print("{x}", .{parts[i]}); | | |
| 370 | if (i != parts.len - 1) { | | |
| 371 | try w.writeAll(":"); | | |
| 372 | } | | |
| 373 | } | | |
| 374 | try w.print("]:{d}", .{a.port}); | | |
| 375 | } | 412 | } |
| 376 | | 413 | |
| 377 | pub fn eql(a: Ip6Address, b: Ip6Address) bool { | 414 | pub fn eql(a: Ip6Address, b: Ip6Address) bool { |
| ... | @@ -471,11 +508,64 @@ pub const Ip6Address = struct { | ... | @@ -471,11 +508,64 @@ pub const Ip6Address = struct { |
| 471 | }; | 508 | }; |
| 472 | }; | 509 | }; |
| 473 | | 510 | |
| | 511 | pub const Interface = struct { |
| | 512 | /// Value 0 indicates `none`. |
| | 513 | index: u32, |
| | 514 | |
| | 515 | pub const none: Interface = .{ .index = 0 }; |
| | 516 | |
| | 517 | pub const Name = struct { |
| | 518 | bytes: [max_len:0]u8, |
| | 519 | |
| | 520 | pub const max_len = std.posix.IFNAMESIZE - 1; |
| | 521 | |
| | 522 | pub fn toSlice(n: *const Name) []const u8 { |
| | 523 | return std.mem.sliceTo(&n.bytes, 0); |
| | 524 | } |
| | 525 | |
| | 526 | pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { |
| | 527 | if (bytes.len > max_len) return error.NameTooLong; |
| | 528 | var result: Name = undefined; |
| | 529 | @memcpy(result.bytes[0..bytes.len], bytes); |
| | 530 | result.bytes[bytes.len] = 0; |
| | 531 | return result; |
| | 532 | } |
| | 533 | |
| | 534 | pub const ResolveError = error{ |
| | 535 | InterfaceNotFound, |
| | 536 | AccessDenied, |
| | 537 | SystemResources, |
| | 538 | } || Io.UnexpectedError || Io.Cancelable; |
| | 539 | |
| | 540 | /// Corresponds to "if_nametoindex" in libc. |
| | 541 | pub fn resolve(n: []const u8, io: Io) ResolveError!Interface { |
| | 542 | return io.vtable.netInterfaceNameResolve(io.userdata, n); |
| | 543 | } |
| | 544 | }; |
| | 545 | |
| | 546 | pub const NameError = Io.UnexpectedError || Io.Cancelable; |
| | 547 | |
| | 548 | /// Asserts not `none`. |
| | 549 | /// |
| | 550 | /// Corresponds to "if_indextoname" in libc. |
| | 551 | pub fn name(i: Interface, io: Io) NameError!Name { |
| | 552 | assert(i.index != 0); |
| | 553 | return io.vtable.netInterfaceName(io.userdata, i); |
| | 554 | } |
| | 555 | |
| | 556 | pub fn isNone(i: Interface) bool { |
| | 557 | return i.index == 0; |
| | 558 | } |
| | 559 | }; |
| | 560 | |
| | 561 | /// An open socket connection with a network protocol that guarantees |
| | 562 | /// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain |
| | 563 | /// socket. |
| 474 | pub const Stream = struct { | 564 | pub const Stream = struct { |
| 475 | /// Underlying platform-defined type which may or may not be | | |
| 476 | /// interchangeable with a file system file descriptor. | | |
| 477 | handle: Handle, | 565 | handle: Handle, |
| 478 | | 566 | |
| | 567 | /// Underlying platform-defined type which may or may not be |
| | 568 | /// interchangeable with a file system file descriptor. |
| 479 | pub const Handle = switch (native_os) { | 569 | pub const Handle = switch (native_os) { |
| 480 | .windows => std.windows.ws2_32.SOCKET, | 570 | .windows => std.windows.ws2_32.SOCKET, |
| 481 | else => std.posix.fd_t, | 571 | else => std.posix.fd_t, |
| ... | @@ -583,17 +673,6 @@ pub const Server = struct { | ... | @@ -583,17 +673,6 @@ pub const Server = struct { |
| 583 | } | 673 | } |
| 584 | }; | 674 | }; |
| 585 | | 675 | |
| 586 | pub const InterfaceIndexError = error{ | | |
| 587 | InterfaceNotFound, | | |
| 588 | AccessDenied, | | |
| 589 | SystemResources, | | |
| 590 | } || Io.UnexpectedError || Io.Cancelable; | | |
| 591 | | | |
| 592 | /// Otherwise known as "if_nametoindex". | | |
| 593 | pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 { | | |
| 594 | return io.vtable.netInterfaceIndex(io.userdata, name); | | |
| 595 | } | | |
| 596 | | | |
| 597 | test { | 676 | test { |
| 598 | _ = HostName; | 677 | _ = HostName; |
| 599 | } | 678 | } |