extern struct Ip6Address [src]
Fields
sa: posix.sockaddr.in6
Members
Source
pub const Ip6Address = extern struct {
sa: posix.sockaddr.in6,
/// Parse a given IPv6 address string into an Address.
/// Assumes the Scope ID of the address is fully numeric.
/// For non-numeric addresses, see `resolveIp6`.
pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address {
var result = Ip6Address{
.sa = posix.sockaddr.in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
var ip_slice: *[16]u8 = result.sa.addr[0..];
var tail: [16]u8 = undefined;
var x: u16 = 0;
var saw_any_digits = false;
var index: u8 = 0;
var scope_id = false;
var abbrv = false;
for (buf, 0..) |c, i| {
if (scope_id) {
if (c >= '0' and c <= '9') {
const digit = c - '0';
{
const ov = @mulWithOverflow(result.sa.scope_id, 10);
if (ov[1] != 0) return error.Overflow;
result.sa.scope_id = ov[0];
}
{
const ov = @addWithOverflow(result.sa.scope_id, digit);
if (ov[1] != 0) return error.Overflow;
result.sa.scope_id = ov[0];
}
} else {
return error.InvalidCharacter;
}
} else if (c == ':') {
if (!saw_any_digits) {
if (abbrv) return error.InvalidCharacter; // ':::'
if (i != 0) abbrv = true;
@memset(ip_slice[index..], 0);
ip_slice = tail[0..];
index = 0;
continue;
}
if (index == 14) {
return error.InvalidEnd;
}
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
x = 0;
saw_any_digits = false;
} else if (c == '%') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
scope_id = true;
saw_any_digits = false;
} else if (c == '.') {
if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
// must start with '::ffff:'
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).sa.addr;
ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
ip_slice[12] = ptr[0];
ip_slice[13] = ptr[1];
ip_slice[14] = ptr[2];
ip_slice[15] = ptr[3];
return result;
} else {
const digit = try std.fmt.charToDigit(c, 16);
{
const ov = @mulWithOverflow(x, 16);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
{
const ov = @addWithOverflow(x, digit);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
saw_any_digits = true;
}
}
if (!saw_any_digits and !abbrv) {
return error.Incomplete;
}
if (!abbrv and index < 14) {
return error.Incomplete;
}
if (index == 14) {
ip_slice[14] = @as(u8, @truncate(x >> 8));
ip_slice[15] = @as(u8, @truncate(x));
return result;
} else {
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
@memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
return result;
}
}
pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address {
// TODO: Unify the implementations of resolveIp6 and parseIp6.
var result = Ip6Address{
.sa = posix.sockaddr.in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
var ip_slice: *[16]u8 = result.sa.addr[0..];
var tail: [16]u8 = undefined;
var x: u16 = 0;
var saw_any_digits = false;
var index: u8 = 0;
var abbrv = false;
var scope_id = false;
var scope_id_value: [posix.IFNAMESIZE - 1]u8 = undefined;
var scope_id_index: usize = 0;
for (buf, 0..) |c, i| {
if (scope_id) {
// Handling of percent-encoding should be for an URI library.
if ((c >= '0' and c <= '9') or
(c >= 'A' and c <= 'Z') or
(c >= 'a' and c <= 'z') or
(c == '-') or (c == '.') or (c == '_') or (c == '~'))
{
if (scope_id_index >= scope_id_value.len) {
return error.Overflow;
}
scope_id_value[scope_id_index] = c;
scope_id_index += 1;
} else {
return error.InvalidCharacter;
}
} else if (c == ':') {
if (!saw_any_digits) {
if (abbrv) return error.InvalidCharacter; // ':::'
if (i != 0) abbrv = true;
@memset(ip_slice[index..], 0);
ip_slice = tail[0..];
index = 0;
continue;
}
if (index == 14) {
return error.InvalidEnd;
}
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
x = 0;
saw_any_digits = false;
} else if (c == '%') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
scope_id = true;
saw_any_digits = false;
} else if (c == '.') {
if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
// must start with '::ffff:'
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).sa.addr;
ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
ip_slice[12] = ptr[0];
ip_slice[13] = ptr[1];
ip_slice[14] = ptr[2];
ip_slice[15] = ptr[3];
return result;
} else {
const digit = try std.fmt.charToDigit(c, 16);
{
const ov = @mulWithOverflow(x, 16);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
{
const ov = @addWithOverflow(x, digit);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
saw_any_digits = true;
}
}
if (!saw_any_digits and !abbrv) {
return error.Incomplete;
}
if (scope_id and scope_id_index == 0) {
return error.Incomplete;
}
var resolved_scope_id: u32 = 0;
if (scope_id_index > 0) {
const scope_id_str = scope_id_value[0..scope_id_index];
resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
if (err != error.InvalidCharacter) return err;
break :blk try if_nametoindex(scope_id_str);
};
}
result.sa.scope_id = resolved_scope_id;
if (index == 14) {
ip_slice[14] = @as(u8, @truncate(x >> 8));
ip_slice[15] = @as(u8, @truncate(x));
return result;
} else {
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
@memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
return result;
}
}
pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address {
return Ip6Address{
.sa = posix.sockaddr.in6{
.addr = addr,
.port = mem.nativeToBig(u16, port),
.flowinfo = flowinfo,
.scope_id = scope_id,
},
};
}
/// Returns the port in native endian.
/// Asserts that the address is ip4 or ip6.
pub fn getPort(self: Ip6Address) u16 {
return mem.bigToNative(u16, self.sa.port);
}
/// `port` is native-endian.
/// Asserts that the address is ip4 or ip6.
pub fn setPort(self: *Ip6Address, port: u16) void {
self.sa.port = mem.nativeToBig(u16, port);
}
pub fn format(
self: Ip6Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self);
_ = options;
const port = mem.bigToNative(u16, self.sa.port);
if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{
self.sa.addr[12],
self.sa.addr[13],
self.sa.addr[14],
self.sa.addr[15],
port,
});
return;
}
const big_endian_parts = @as(*align(1) const [8]u16, @ptrCast(&self.sa.addr));
const native_endian_parts = switch (native_endian) {
.big => big_endian_parts.*,
.little => blk: {
var buf: [8]u16 = undefined;
for (big_endian_parts, 0..) |part, i| {
buf[i] = mem.bigToNative(u16, part);
}
break :blk buf;
},
};
// Find the longest zero run
var longest_start: usize = 8;
var longest_len: usize = 0;
var current_start: usize = 0;
var current_len: usize = 0;
for (native_endian_parts, 0..) |part, i| {
if (part == 0) {
if (current_len == 0) {
current_start = i;
}
current_len += 1;
if (current_len > longest_len) {
longest_start = current_start;
longest_len = current_len;
}
} else {
current_len = 0;
}
}
// Only compress if the longest zero run is 2 or more
if (longest_len < 2) {
longest_start = 8;
longest_len = 0;
}
try out_stream.writeAll("[");
var i: usize = 0;
var abbrv = false;
while (i < native_endian_parts.len) : (i += 1) {
if (i == longest_start) {
// Emit "::" for the longest zero run
if (!abbrv) {
try out_stream.writeAll(if (i == 0) "::" else ":");
abbrv = true;
}
i += longest_len - 1; // Skip the compressed range
continue;
}
if (abbrv) {
abbrv = false;
}
try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
if (i != native_endian_parts.len - 1) {
try out_stream.writeAll(":");
}
}
try std.fmt.format(out_stream, "]:{}", .{port});
}
pub fn getOsSockLen(self: Ip6Address) posix.socklen_t {
_ = self;
return @sizeOf(posix.sockaddr.in6);
}
}