| ... | ... | @@ -26,10 +26,12 @@ pub const IpAddress = union(enum) { |
| 26 | 26 | pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; |
| 27 | 27 | |
| 28 | 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 | 33 | pub fn parse(name: []const u8, port: u16) !IpAddress { |
| 30 | | if (Ip4Address.parse(name, port)) |ip4| { |
| 31 | | return .{ .ip4 = ip4 }; |
| 32 | | } else |err| switch (err) { |
| 34 | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { |
| 33 | 35 | error.Overflow, |
| 34 | 36 | error.InvalidEnd, |
| 35 | 37 | error.InvalidCharacter, |
| ... | ... | @@ -38,26 +40,41 @@ pub const IpAddress = union(enum) { |
| 38 | 40 | => {}, |
| 39 | 41 | } |
| 40 | 42 | |
| 41 | | if (Ip6Address.parse(name, port)) |ip6| { |
| 42 | | return .{ .ip6 = ip6 }; |
| 43 | | } else |err| switch (err) { |
| 43 | return parseIp6(name, port); |
| 44 | } |
| 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 | 65 | error.Overflow, |
| 45 | 66 | error.InvalidEnd, |
| 46 | 67 | error.InvalidCharacter, |
| 47 | 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 { |
| 56 | | return .{ .ip6 = try Ip6Address.parse(buffer, port) }; |
| 57 | | } |
| 58 | | |
| 59 | | pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress { |
| 60 | | return .{ .ip4 = try Ip4Address.parse(buffer, port) }; |
| 76 | pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress { |
| 77 | return .{ .ip6 = try Ip6Address.resolve(io, text, port) }; |
| 61 | 78 | } |
| 62 | 79 | |
| 63 | 80 | /// Returns the port in native endian. |
| ... | ... | @@ -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 | 107 | pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void { |
| 78 | 108 | switch (a) { |
| 79 | 109 | inline .ip4, .ip6 => |x| return x.format(w), |
| ... | ... | @@ -99,11 +129,12 @@ pub const IpAddress = union(enum) { |
| 99 | 129 | } |
| 100 | 130 | }; |
| 101 | 131 | |
| 132 | /// An IPv4 address in binary memory layout. |
| 102 | 133 | pub const Ip4Address = struct { |
| 103 | 134 | bytes: [4]u8, |
| 104 | 135 | port: u16, |
| 105 | 136 | |
| 106 | | pub fn localhost(port: u16) Ip4Address { |
| 137 | pub fn loopback(port: u16) Ip4Address { |
| 107 | 138 | return .{ |
| 108 | 139 | .bytes = .{ 127, 0, 0, 1 }, |
| 109 | 140 | .port = port, |
| ... | ... | @@ -162,21 +193,14 @@ pub const Ip4Address = struct { |
| 162 | 193 | } |
| 163 | 194 | }; |
| 164 | 195 | |
| 196 | /// An IPv6 address in binary memory layout. |
| 165 | 197 | pub const Ip6Address = struct { |
| 166 | 198 | /// Native endian |
| 167 | 199 | port: u16, |
| 168 | 200 | /// Big endian |
| 169 | 201 | bytes: [16]u8, |
| 170 | | flowinfo: u32 = 0, |
| 171 | | scope_id: u32 = 0, |
| 172 | | |
| 173 | | pub const ParseError = error{ |
| 174 | | Overflow, |
| 175 | | InvalidCharacter, |
| 176 | | InvalidEnd, |
| 177 | | InvalidIpv4Mapping, |
| 178 | | Incomplete, |
| 179 | | }; |
| 202 | flow: u32 = 0, |
| 203 | interface: Interface = .none, |
| 180 | 204 | |
| 181 | 205 | pub const Policy = struct { |
| 182 | 206 | addr: [16]u8, |
| ... | ... | @@ -186,192 +210,205 @@ pub const Ip6Address = struct { |
| 186 | 210 | label: u8, |
| 187 | 211 | }; |
| 188 | 212 | |
| 189 | | pub fn localhost(port: u16) Ip6Address { |
| 213 | pub fn loopback(port: u16) Ip6Address { |
| 190 | 214 | return .{ |
| 191 | 215 | .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, |
| 192 | 216 | .port = port, |
| 193 | 217 | }; |
| 194 | 218 | } |
| 195 | 219 | |
| 196 | | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { |
| 197 | | var result: Ip6Address = .{ |
| 198 | | .port = port, |
| 199 | | .bytes = undefined, |
| 220 | /// An IPv6 address but with `Interface` as a name rather than index. |
| 221 | pub const Unresolved = struct { |
| 222 | /// Big endian |
| 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; |
| 206 | | var saw_any_digits = false; |
| 207 | | var index: u8 = 0; |
| 208 | | var scope_id = false; |
| 209 | | var abbrv = false; |
| 210 | | for (buffer, 0..) |c, i| { |
| 211 | | if (scope_id) { |
| 212 | | if (c >= '0' and c <= '9') { |
| 213 | | const digit = c - '0'; |
| 214 | | { |
| 215 | | const ov = @mulWithOverflow(result.scope_id, 10); |
| 216 | | if (ov[1] != 0) return error.Overflow; |
| 217 | | result.scope_id = ov[0]; |
| 218 | | } |
| 219 | | { |
| 220 | | const ov = @addWithOverflow(result.scope_id, digit); |
| 221 | | if (ov[1] != 0) return error.Overflow; |
| 222 | | result.scope_id = ov[0]; |
| 268 | pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved { |
| 269 | if (a.interface.isNone()) return .{ |
| 270 | .bytes = a.bytes, |
| 271 | .interface_name = null, |
| 272 | }; |
| 273 | return .{ |
| 274 | .bytes = a.bytes, |
| 275 | .interface_name = try a.interface.name(io), |
| 276 | }; |
| 277 | } |
| 278 | |
| 279 | pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { |
| 280 | const bytes = &u.bytes; |
| 281 | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { |
| 282 | try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); |
| 283 | } else { |
| 284 | const parts: [8]u16 = .{ |
| 285 | std.mem.readInt(u16, bytes[0..2], .big), |
| 286 | std.mem.readInt(u16, bytes[2..4], .big), |
| 287 | std.mem.readInt(u16, bytes[4..6], .big), |
| 288 | std.mem.readInt(u16, bytes[6..8], .big), |
| 289 | std.mem.readInt(u16, bytes[8..10], .big), |
| 290 | std.mem.readInt(u16, bytes[10..12], .big), |
| 291 | std.mem.readInt(u16, bytes[12..14], .big), |
| 292 | std.mem.readInt(u16, bytes[14..16], .big), |
| 293 | }; |
| 294 | |
| 295 | // Find the longest zero run |
| 296 | var longest_start: usize = 8; |
| 297 | var longest_len: usize = 0; |
| 298 | var current_start: usize = 0; |
| 299 | var current_len: usize = 0; |
| 300 | |
| 301 | for (parts, 0..) |part, i| { |
| 302 | if (part == 0) { |
| 303 | if (current_len == 0) { |
| 304 | current_start = i; |
| 305 | } |
| 306 | current_len += 1; |
| 307 | if (current_len > longest_len) { |
| 308 | longest_start = current_start; |
| 309 | longest_len = current_len; |
| 310 | } |
| 311 | } else { |
| 312 | current_len = 0; |
| 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; |
| 245 | | saw_any_digits = false; |
| 246 | | } else if (c == '%') { |
| 247 | | if (!saw_any_digits) { |
| 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; |
| 316 | // Only compress if the longest zero run is 2 or more |
| 317 | if (longest_len < 2) { |
| 318 | longest_start = 8; |
| 319 | longest_len = 0; |
| 256 | 320 | } |
| 257 | | const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1; |
| 258 | | const addr = (Ip4Address.parse(buffer[start_index..], 0) catch { |
| 259 | | return error.InvalidIpv4Mapping; |
| 260 | | }).bytes; |
| 261 | | ip_slice = result.bytes[0..]; |
| 262 | | ip_slice[10] = 0xff; |
| 263 | | ip_slice[11] = 0xff; |
| 264 | | |
| 265 | | ip_slice[12] = addr[0]; |
| 266 | | ip_slice[13] = addr[1]; |
| 267 | | ip_slice[14] = addr[2]; |
| 268 | | ip_slice[15] = addr[3]; |
| 269 | | return result; |
| 270 | | } else { |
| 271 | | const digit = try std.fmt.charToDigit(c, 16); |
| 272 | | { |
| 273 | | const ov = @mulWithOverflow(x, 16); |
| 274 | | if (ov[1] != 0) return error.Overflow; |
| 275 | | x = ov[0]; |
| 276 | | } |
| 277 | | { |
| 278 | | const ov = @addWithOverflow(x, digit); |
| 279 | | if (ov[1] != 0) return error.Overflow; |
| 280 | | x = ov[0]; |
| 321 | |
| 322 | try w.writeAll("["); |
| 323 | var i: usize = 0; |
| 324 | var abbrv = false; |
| 325 | while (i < parts.len) : (i += 1) { |
| 326 | if (i == longest_start) { |
| 327 | // Emit "::" for the longest zero run |
| 328 | if (!abbrv) { |
| 329 | try w.writeAll(if (i == 0) "::" else ":"); |
| 330 | abbrv = true; |
| 331 | } |
| 332 | i += longest_len - 1; // Skip the compressed range |
| 333 | continue; |
| 334 | } |
| 335 | if (abbrv) { |
| 336 | abbrv = false; |
| 337 | } |
| 338 | try w.print("{x}", .{parts[i]}); |
| 339 | if (i != parts.len - 1) { |
| 340 | try w.writeAll(":"); |
| 341 | } |
| 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) { |
| 287 | | return error.Incomplete; |
| 288 | | } |
| 289 | | if (!abbrv and index < 14) { |
| 290 | | return error.Incomplete; |
| 291 | | } |
| 348 | pub const ParseError = error{ |
| 349 | /// If this is returned, more detailed diagnostics can be obtained by |
| 350 | /// calling `Ip6Address.Parsed.init`. |
| 351 | ParseFailed, |
| 352 | /// If this is returned, the IPv6 address had a scope id on it ("%foo" |
| 353 | /// at the end) which requires calling `resolve`. |
| 354 | UnresolvedScope, |
| 355 | }; |
| 292 | 356 | |
| 293 | | if (index == 14) { |
| 294 | | ip_slice[14] = @as(u8, @truncate(x >> 8)); |
| 295 | | ip_slice[15] = @as(u8, @truncate(x)); |
| 296 | | return result; |
| 297 | | } else { |
| 298 | | ip_slice[index] = @as(u8, @truncate(x >> 8)); |
| 299 | | index += 1; |
| 300 | | ip_slice[index] = @as(u8, @truncate(x)); |
| 301 | | index += 1; |
| 302 | | @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]); |
| 303 | | return result; |
| 357 | /// This is a pure function but it cannot handle IPv6 addresses that have |
| 358 | /// scope ids ("%foo" at the end). To also handle those, `resolve` must be |
| 359 | /// called instead. |
| 360 | pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { |
| 361 | switch (Unresolved.parse(buffer)) { |
| 362 | .success => |p| return .{ |
| 363 | .bytes = p.bytes, |
| 364 | .port = port, |
| 365 | .interface = if (p.interface_name != null) return error.UnresolvedScope else .none, |
| 366 | }, |
| 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 { |
| 308 | | const bytes = &a.bytes; |
| 309 | | if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { |
| 310 | | try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{ |
| 311 | | bytes[12], bytes[13], bytes[14], bytes[15], a.port, |
| 312 | | }); |
| 313 | | return; |
| 314 | | } |
| 315 | | const parts: [8]u16 = .{ |
| 316 | | std.mem.readInt(u16, bytes[0..2], .big), |
| 317 | | std.mem.readInt(u16, bytes[2..4], .big), |
| 318 | | std.mem.readInt(u16, bytes[4..6], .big), |
| 319 | | std.mem.readInt(u16, bytes[6..8], .big), |
| 320 | | std.mem.readInt(u16, bytes[8..10], .big), |
| 321 | | std.mem.readInt(u16, bytes[10..12], .big), |
| 322 | | std.mem.readInt(u16, bytes[12..14], .big), |
| 323 | | std.mem.readInt(u16, bytes[14..16], .big), |
| 372 | pub const ResolveError = error{ |
| 373 | /// If this is returned, more detailed diagnostics can be obtained by |
| 374 | /// calling the `Parsed.init` function. |
| 375 | ParseFailed, |
| 376 | } || Interface.Name.ResolveError; |
| 377 | |
| 378 | /// This function requires an `Io` parameter because it must query the operating |
| 379 | /// system to convert interface name to index. For example, in |
| 380 | /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by |
| 381 | /// creating a socket and then using an `ioctl` syscall. |
| 382 | pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address { |
| 383 | return switch (Unresolved.parse(buffer)) { |
| 384 | .success => |p| return .{ |
| 385 | .bytes = p.bytes, |
| 386 | .port = port, |
| 387 | .interface = if (p.interface_name) |n| try n.resolve(io) else .none, |
| 388 | }, |
| 389 | else => return error.ParseFailed, |
| 324 | 390 | }; |
| 391 | } |
| 325 | 392 | |
| 326 | | // Find the longest zero run |
| 327 | | var longest_start: usize = 8; |
| 328 | | var longest_len: usize = 0; |
| 329 | | var current_start: usize = 0; |
| 330 | | var current_len: usize = 0; |
| 393 | pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError; |
| 331 | 394 | |
| 332 | | for (parts, 0..) |part, i| { |
| 333 | | if (part == 0) { |
| 334 | | if (current_len == 0) { |
| 335 | | current_start = i; |
| 336 | | } |
| 337 | | current_len += 1; |
| 338 | | if (current_len > longest_len) { |
| 339 | | longest_start = current_start; |
| 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 | | } |
| 395 | /// Includes the optional scope ("%foo" at the end). |
| 396 | /// |
| 397 | /// See `format` for an alternative that omits scopes and does |
| 398 | /// not require an `Io` parameter. |
| 399 | pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void { |
| 400 | const u: Unresolved = try .fromAddress(io); |
| 401 | try w.print("[{f}]:{d}", .{ u, a.port }); |
| 402 | } |
| 352 | 403 | |
| 353 | | try w.writeAll("["); |
| 354 | | var i: usize = 0; |
| 355 | | var abbrv = false; |
| 356 | | while (i < parts.len) : (i += 1) { |
| 357 | | if (i == longest_start) { |
| 358 | | // Emit "::" for the longest zero run |
| 359 | | if (!abbrv) { |
| 360 | | try w.writeAll(if (i == 0) "::" else ":"); |
| 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}); |
| 404 | /// See `formatResolved` for an alternative that additionally prints the optional |
| 405 | /// scope at the end of addresses and requires an `Io` parameter. |
| 406 | pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { |
| 407 | const u: Unresolved = .{ |
| 408 | .bytes = a.bytes, |
| 409 | .interface_name = null, |
| 410 | }; |
| 411 | try w.print("[{f}]:{d}", .{ u, a.port }); |
| 375 | 412 | } |
| 376 | 413 | |
| 377 | 414 | pub fn eql(a: Ip6Address, b: Ip6Address) bool { |
| ... | ... | @@ -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 | 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 | 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 | 569 | pub const Handle = switch (native_os) { |
| 480 | 570 | .windows => std.windows.ws2_32.SOCKET, |
| 481 | 571 | else => std.posix.fd_t, |
| ... | ... | @@ -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 | 676 | test { |
| 598 | 677 | _ = HostName; |
| 599 | 678 | } |