//! DNS wire-format enumerations and protocol limits (RFC 1035 §3.2, §4.1.1).
//! Pure: no allocation, no `std.Io`.

const std = @import("std");

/// RR TYPE / QTYPE. Non-exhaustive: unknown types pass through opaquely.
pub const Type = enum(u16) {
    a = 1,
    ns = 2,
    cname = 5,
    soa = 6,
    ptr = 12,
    mx = 15,
    txt = 16,
    aaaa = 28,
    srv = 33,
    opt = 41,
    svcb = 64,
    https = 65,
    any = 255,
    _,
};

/// RR CLASS / QCLASS. Non-exhaustive.
pub const Class = enum(u16) {
    in = 1,
    ch = 3,
    hs = 4,
    any = 255,
    _,
};

/// The 4-bit header RCODE. EDNS(0) extends this to 12 bits; the upper 8 bits
/// live in the OPT record and are handled in edns.zig.
pub const Rcode = enum(u4) {
    no_error = 0,
    form_err = 1,
    serv_fail = 2,
    nx_domain = 3,
    not_imp = 4,
    refused = 5,
    yx_domain = 6,
    yx_rr_set = 7,
    nx_rr_set = 8,
    not_auth = 9,
    not_zone = 10,
    _,
};

/// The 4-bit header OPCODE.
pub const Opcode = enum(u4) {
    query = 0,
    iquery = 1,
    status = 2,
    notify = 4,
    update = 5,
    _,
};

/// Maximum length of a name in uncompressed wire form, terminating zero included.
pub const max_name_len = 255;

/// Maximum length of a single label's data, length byte excluded.
pub const max_label_len = 63;

/// Compression pointers must always target a strictly lower offset, so a chain
/// terminates on its own. This cap bounds the work a single name can cost.
pub const max_compression_jumps = 32;

/// Pre-EDNS UDP payload limit (RFC 1035 §4.2.1).
pub const max_udp_payload = 512;

/// Fixed size of the DNS message header.
pub const header_len = 12;

test "type values match the wire numbers" {
    try std.testing.expectEqual(@as(u16, 1), @intFromEnum(Type.a));
    try std.testing.expectEqual(@as(u16, 28), @intFromEnum(Type.aaaa));
    try std.testing.expectEqual(@as(u16, 41), @intFromEnum(Type.opt));
    try std.testing.expectEqual(@as(u16, 64), @intFromEnum(Type.svcb));
    try std.testing.expectEqual(@as(u16, 65), @intFromEnum(Type.https));
    try std.testing.expectEqual(@as(u16, 255), @intFromEnum(Type.any));
}

test "class values match the wire numbers" {
    try std.testing.expectEqual(@as(u16, 1), @intFromEnum(Class.in));
    try std.testing.expectEqual(@as(u16, 3), @intFromEnum(Class.ch));
    try std.testing.expectEqual(@as(u16, 4), @intFromEnum(Class.hs));
    try std.testing.expectEqual(@as(u16, 255), @intFromEnum(Class.any));
}

test "rcode and opcode values match the wire numbers" {
    try std.testing.expectEqual(@as(u4, 0), @intFromEnum(Rcode.no_error));
    try std.testing.expectEqual(@as(u4, 3), @intFromEnum(Rcode.nx_domain));
    try std.testing.expectEqual(@as(u4, 5), @intFromEnum(Rcode.refused));
    try std.testing.expectEqual(@as(u4, 0), @intFromEnum(Opcode.query));
    try std.testing.expectEqual(@as(u4, 4), @intFromEnum(Opcode.notify));
    try std.testing.expectEqual(@as(u4, 5), @intFromEnum(Opcode.update));
}

test "unknown enum values round-trip" {
    const unknown_type: Type = @enumFromInt(9999);
    try std.testing.expectEqual(@as(u16, 9999), @intFromEnum(unknown_type));

    const unknown_class: Class = @enumFromInt(1234);
    try std.testing.expectEqual(@as(u16, 1234), @intFromEnum(unknown_class));

    const unknown_rcode: Rcode = @enumFromInt(15);
    try std.testing.expectEqual(@as(u4, 15), @intFromEnum(unknown_rcode));

    const unknown_opcode: Opcode = @enumFromInt(3);
    try std.testing.expectEqual(@as(u4, 3), @intFromEnum(unknown_opcode));
}

test "limits" {
    try std.testing.expectEqual(255, max_name_len);
    try std.testing.expectEqual(63, max_label_len);
    try std.testing.expectEqual(32, max_compression_jumps);
    try std.testing.expectEqual(512, max_udp_payload);
    try std.testing.expectEqual(12, header_len);
}
