//! Question section entries (RFC 1035 ยง4.1.2). Pure: no allocation, no //! `std.Io` beyond writing encoded bytes to a caller's writer. const std = @import("std"); const types = @import("types.zig"); const name = @import("name.zig"); const Writer = std.Io.Writer; pub const Question = struct { name: name.Name, qtype: types.Type, qclass: types.Class, }; pub const ParseError = name.ParseError; pub const Parsed = struct { question: Question, /// Offset just past the question as it appears at `offset`; follows the /// same convention as `name.Parsed.end` for a compressed name. end: usize, }; pub fn parse(packet: []const u8, offset: usize) ParseError!Parsed { const parsed_name = try name.parse(packet, offset); const fixed = parsed_name.end; if (fixed + 4 > packet.len) return error.Truncated; return .{ .question = .{ .name = parsed_name.name, .qtype = @enumFromInt(std.mem.readInt(u16, packet[fixed..][0..2], .big)), .qclass = @enumFromInt(std.mem.readInt(u16, packet[fixed + 2 ..][0..2], .big)), }, .end = fixed + 4, }; } pub fn encode(q: Question, w: *Writer) Writer.Error!void { try name.encode(q.name, w); try w.writeInt(u16, @intFromEnum(q.qtype), .big); try w.writeInt(u16, @intFromEnum(q.qclass), .big); } const testing = std.testing; test "parse an uncompressed question" { const packet = "\x07example\x03com\x00\x00\x01\x00\x01"; const r = try parse(packet, 0); try testing.expectEqualSlices( u8, (try name.fromText("example.com")).wire(), r.question.name.wire(), ); try testing.expectEqual(types.Type.a, r.question.qtype); try testing.expectEqual(types.Class.in, r.question.qclass); try testing.expectEqual(@as(usize, 17), r.end); } test "parse a question whose name is compressed" { // "com" at offset 0, then a question at offset 5 naming "example.com" // through a pointer. const packet = "\x03com\x00" ++ "\x07example\xc0\x00\x00\x1c\x00\x01"; const r = try parse(packet, 5); try testing.expectEqualSlices( u8, (try name.fromText("example.com")).wire(), r.question.name.wire(), ); try testing.expectEqual(types.Type.aaaa, r.question.qtype); try testing.expectEqual(types.Class.in, r.question.qclass); try testing.expectEqual(@as(usize, 19), r.end); } test "parse keeps unknown type and class values" { const packet = "\x00\x12\x34\x56\x78"; const r = try parse(packet, 0); try testing.expect(r.question.name.isRoot()); try testing.expectEqual(@as(u16, 0x1234), @intFromEnum(r.question.qtype)); try testing.expectEqual(@as(u16, 0x5678), @intFromEnum(r.question.qclass)); try testing.expectEqual(@as(usize, 5), r.end); } test "parse rejects a truncated fixed field" { const full = "\x07example\x03com\x00\x00\x01\x00\x01"; var i: usize = 13; while (i < full.len) : (i += 1) { try testing.expectError(error.Truncated, parse(full[0..i], 0)); } } test "parse propagates a name error" { try testing.expectError(error.BadPointer, parse("\xc0\x00\x00\x01\x00\x01", 0)); try testing.expectError(error.Truncated, parse("\x07exa", 0)); try testing.expectError(error.LabelTooLong, parse("\x40abc\x00\x00\x01\x00\x01", 0)); } test "encode round-trips" { const original: Question = .{ .name = try name.fromText("www.example.com"), .qtype = .aaaa, .qclass = .in, }; var buf: [512]u8 = undefined; var w = Writer.fixed(&buf); try encode(original, &w); const bytes = w.buffered(); try testing.expectEqual(@as(usize, 21), bytes.len); const r = try parse(bytes, 0); try testing.expectEqualSlices(u8, original.name.wire(), r.question.name.wire()); try testing.expectEqual(original.qtype, r.question.qtype); try testing.expectEqual(original.qclass, r.question.qclass); try testing.expectEqual(bytes.len, r.end); } test "encode writes big-endian fixed fields" { const q: Question = .{ .name = try name.fromText("."), .qtype = @enumFromInt(0x1234), .qclass = @enumFromInt(0x5678), }; var buf: [16]u8 = undefined; var w = Writer.fixed(&buf); try encode(q, &w); try testing.expectEqualSlices(u8, "\x00\x12\x34\x56\x78", w.buffered()); } test "encode reports a short buffer" { const q: Question = .{ .name = try name.fromText("example.com"), .qtype = .a, .qclass = .in, }; var buf: [16]u8 = undefined; var w = Writer.fixed(&buf); try testing.expectError(error.WriteFailed, encode(q, &w)); }