139 lines
4.5 KiB
Zig
139 lines
4.5 KiB
Zig
//! 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));
|
|
}
|