Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Ip6Address

An IPv6 address in binary memory layout.

net.Ip6Address
pub const Ip6Address = struct

File

lib/std/Io/net.zig:435

Code

pub const Ip6Address = struct {
    /// Native endian
    port: u16,
    /// Big endian
    bytes: [16]u8,
    flow: u32 = 0,
    interface: Interface = .none,

    pub const Policy = struct {
        addr: [16]u8,
        len: u8,
        mask: u8,
        prec: u8,
        label: u8,
    };

    pub fn loopback(port: u16) Ip6Address {
        return .{
            .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
            .port = port,
        };
    }

    pub fn unspecified(port: u16) Ip6Address {
        return .{
            .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
            .port = port,
        };
    }

    /// Constructs an IPv4-mapped IPv6 address.
    pub fn fromIp4(ip4: Ip4Address) Ip6Address {
        return .{
            .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff } ++ ip4.bytes,
            .port = ip4.port,
        };
    }

    /// Given an `IpAddress`, converts it to an `Ip6Address` directly, or via
    /// constructing an IPv4-mapped IPv6 address.
    pub fn fromAny(addr: IpAddress) Ip6Address {
        return switch (addr) {
            .ip4 => |ip4| fromIp4(ip4),
            .ip6 => |ip6| ip6,
        };
    }

    /// An IPv6 address but with `Interface` as a name rather than index.
    pub const Unresolved = struct {
        /// Big endian
        bytes: [16]u8,
        /// Has not been checked to be a valid native interface name.
        /// Externally managed memory.
        interface_name: ?[]const u8,

        pub const Parsed = union(enum) {
            success: Unresolved,
            invalid_byte: usize,
            incomplete,
            junk_after_end: usize,
            interface_name_oversized: usize,
            invalid_ip4_mapping: usize,
            overflow: usize,
        };

        pub fn parse(text: []const u8) Parsed {
            if (text.len < 2) return .incomplete;
            const ip4_prefix = "::ffff:";
            if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
                const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
                    return .{ .invalid_ip4_mapping = ip4_prefix.len };
                const b = parsed.bytes;
                return .{ .success = .{
                    .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
                    .interface_name = null,
                } };
            }
            // Has to be u16 elements to handle 3-digit hex numbers from compression.
            var parts: [8]u16 = @splat(0);
            var parts_i: u8 = 0;
            var text_i: u8 = 0;
            var digit_i: u8 = 0;
            var compress_start: ?u8 = null;
            var interface_name_text: ?[]const u8 = null;
            const State = union(enum) { digit, end };
            state: switch (State.digit) {
                .digit => c: switch (text[text_i]) {
                    'a'...'f' => |c| {
                        const digit = c - 'a' + 10;
                        parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
                            .overflow = text_i,
                        }) + digit;
                        if (digit_i == 4) return .{ .invalid_byte = text_i };
                        digit_i += 1;
                        text_i += 1;
                        if (text.len - text_i == 0) {
                            parts_i += 1;
                            continue :state .end;
                        }
                        continue :c text[text_i];
                    },
                    'A'...'F' => |c| continue :c c - 'A' + 'a',
                    '0'...'9' => |c| {
                        const digit = c - '0';
                        parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
                            .overflow = text_i,
                        }) + digit;
                        if (digit_i == 4) return .{ .invalid_byte = text_i };
                        digit_i += 1;
                        text_i += 1;
                        if (text.len - text_i == 0) {
                            parts_i += 1;
                            continue :state .end;
                        }
                        continue :c text[text_i];
                    },
                    ':' => {
                        if (digit_i == 0) {
                            if (compress_start != null) return .{ .invalid_byte = text_i };
                            if (text_i == 0) {
                                text_i += 1;
                                if (text[text_i] != ':') return .{ .invalid_byte = text_i };
                                assert(parts_i == 0);
                            }
                            compress_start = parts_i;
                            text_i += 1;
                            if (text.len - text_i == 0) continue :state .end;
                            continue :c text[text_i];
                        } else {
                            parts_i += 1;
                            if (parts.len - parts_i == 0) continue :state .end;
                            digit_i = 0;
                            text_i += 1;
                            if (text.len - text_i == 0) return .incomplete;
                            continue :c text[text_i];
                        }
                    },
                    '%' => {
                        if (digit_i == 0) return .{ .invalid_byte = text_i };
                        parts_i += 1;
                        text_i += 1;
                        const name = text[text_i..];
                        if (name.len == 0) return .incomplete;
                        interface_name_text = name;
                        text_i = std.math.cast(u8, text.len) orelse return .{ .overflow = text.len };
                        continue :state .end;
                    },
                    else => return .{ .invalid_byte = text_i },
                },
                .end => {
                    if (text.len - text_i != 0) return .{ .junk_after_end = text_i };
                    const remaining = parts.len - parts_i;
                    if (compress_start) |s| {
                        const src = parts[s..parts_i];
                        @memmove(parts[parts.len - src.len ..], src);
                        @memset(parts[s..][0..remaining], 0);
                    } else {
                        if (remaining != 0) return .incomplete;
                    }

                    for (&parts) |*part| part.* = @byteSwap(part.*);

                    return .{ .success = .{
                        .bytes = @bitCast(parts),
                        .interface_name = interface_name_text,
                    } };
                },
            }
        }

        pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
            const bytes = &u.bytes;
            if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
                try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
            } else {
                const parts: [8]u16 = .{
                    std.mem.readInt(u16, bytes[0..2], .big),
                    std.mem.readInt(u16, bytes[2..4], .big),
                    std.mem.readInt(u16, bytes[4..6], .big),
                    std.mem.readInt(u16, bytes[6..8], .big),
                    std.mem.readInt(u16, bytes[8..10], .big),
                    std.mem.readInt(u16, bytes[10..12], .big),
                    std.mem.readInt(u16, bytes[12..14], .big),
                    std.mem.readInt(u16, bytes[14..16], .big),
                };

                // 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 (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;
                }

                var i: usize = 0;
                while (parts.len - i != 0) : (i += 1) {
                    if (i == longest_start) {
                        // Emit "::" for the longest zero run
                        try w.writeAll(if (i == 0) "::" else ":");
                        i += longest_len - 1; // Skip the compressed range
                        continue;
                    }
                    try w.print("{x}", .{parts[i]});
                    if (i != parts.len - 1) {
                        try w.writeAll(":");
                    }
                }
            }
            if (u.interface_name) |n| try w.print("%{s}", .{n});
        }
    };

    pub const ParseError = error{
        /// If this is returned, more detailed diagnostics can be obtained by
        /// calling `Ip6Address.Parsed.init`.
        ParseFailed,
        /// If this is returned, the IPv6 address had a scope id on it ("%foo"
        /// at the end) which requires calling `resolve`.
        UnresolvedScope,
    };

    /// This is a pure function but it cannot handle IPv6 addresses that have
    /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
    /// called instead, or the lower level `Unresolved` API may be used.
    pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
        switch (Unresolved.parse(buffer)) {
            .success => |p| return .{
                .bytes = p.bytes,
                .port = port,
                .interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
            },
            else => return error.ParseFailed,
        }
        return .{ .ip6 = try Ip6Address.parse(buffer, port) };
    }

    pub const ResolveError = error{
        /// If this is returned, more detailed diagnostics can be obtained by
        /// calling the `Parsed.init` function.
        ParseFailed,
        /// The interface name is longer than the host operating system supports.
        NameTooLong,
    } || Interface.Name.ResolveError;

    /// This function requires an `Io` parameter because it must query the operating
    /// system to convert interface name to index. For example, in
    /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
    /// creating a socket and then using an `ioctl` syscall.
    pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
        return switch (Unresolved.parse(buffer)) {
            .success => |p| return .{
                .bytes = p.bytes,
                .port = port,
                .interface = i: {
                    const text = p.interface_name orelse break :i .none;
                    const name: Interface.Name = try .fromSlice(text);
                    break :i try name.resolve(io);
                },
            },
            else => return error.ParseFailed,
        };
    }

    pub const FormatError = Io.Writer.Error || Interface.NameError;

    /// Includes the optional scope ("%foo" at the end).
    ///
    /// See `format` for an alternative that omits scopes and does
    /// not require an `Io` parameter.
    pub fn formatResolved(a: *const Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
        const interface_name = if (a.interface.isNone()) null else try a.interface.name(io);
        const u: Unresolved = .{
            .bytes = a.bytes,
            .interface_name = if (interface_name) |name| name.toSlice() else null,
        };
        try w.print("[{f}]:{d}", .{ u, a.port });
    }

    /// See `formatResolved` for an alternative that additionally prints the optional
    /// scope at the end of addresses and requires an `Io` parameter.
    pub fn format(a: *const Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
        const u: Unresolved = .{ .bytes = a.bytes, .interface_name = null };
        try w.print("[{f}]:{d}", .{ u, a.port });
    }

    pub fn eql(a: Ip6Address, b: Ip6Address) bool {
        return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);
    }

    pub fn isMultiCast(a: Ip6Address) bool {
        return a.bytes[0] == 0xff;
    }

    pub fn isLinkLocal(a: Ip6Address) bool {
        const b = &a.bytes;
        return b[0] == 0xfe and (b[1] & 0xc0) == 0x80;
    }

    pub fn isLoopBack(a: Ip6Address) bool {
        const b = &a.bytes;
        return b[0] == 0 and b[1] == 0 and
            b[2] == 0 and
            b[12] == 0 and b[13] == 0 and
            b[14] == 0 and b[15] == 1;
    }

    pub fn isSiteLocal(a: Ip6Address) bool {
        const b = &a.bytes;
        return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0;
    }

    pub fn policy(a: Ip6Address) *const Policy {
        const b = &a.bytes;
        for (&defined_policies) |*p| {
            if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue;
            if ((b[p.len] & p.mask) != p.addr[p.len]) continue;
            return p;
        }
        unreachable;
    }

    pub fn scope(a: Ip6Address) u8 {
        if (isMultiCast(a)) return a.bytes[1] & 15;
        if (isLinkLocal(a)) return 2;
        if (isLoopBack(a)) return 2;
        if (isSiteLocal(a)) return 5;
        return 14;
    }

    const defined_policies = [_]Policy{
        .{
            .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
            .len = 15,
            .mask = 0xff,
            .prec = 50,
            .label = 0,
        },
        .{
            .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
            .len = 11,
            .mask = 0xff,
            .prec = 35,
            .label = 4,
        },
        .{
            .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
            .len = 1,
            .mask = 0xff,
            .prec = 30,
            .label = 2,
        },
        .{
            .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
            .len = 3,
            .mask = 0xff,
            .prec = 5,
            .label = 5,
        },
        .{
            .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
            .len = 0,
            .mask = 0xfe,
            .prec = 3,
            .label = 13,
        },
        //  These are deprecated and/or returned to the address
        //  pool, so despite the RFC, treating them as special
        //  is probably wrong.
        // { "", 11, 0xff, 1, 3 },
        // { "\xfe\xc0", 1, 0xc0, 1, 11 },
        // { "\x3f\xfe", 1, 0xff, 1, 12 },
        // Last rule must match all addresses to stop loop.
        .{
            .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
            .len = 0,
            .mask = 0,
            .prec = 40,
            .label = 1,
        },
    };
}