-- SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin -- SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later -- Licensed under OWL v1.0+. See LICENSE. local testimony = require("testimony") local crypto = require("crypto") local message = require("dns.message") local types = require("dns.types") local fixtures = dofile("tests/dns/fixtures.lua") local expected = dofile("tests/dns/fixtures_expected.lua") local testify = testimony.new("== dns.message ==") -- ========================================================================= -- Error handling -- ========================================================================= testify:that("decode rejects empty string", function() local msg, err = message.decode("") testimony.assert_nil(msg) testimony.assert_match("too short", err) end) testify:that("decode rejects nil", function() local msg, err = message.decode(nil) testimony.assert_nil(msg) testimony.assert_not_nil(err) end) testify:that("decode rejects < 12 bytes", function() local msg, err = message.decode(string.rep("\0", 11)) testimony.assert_nil(msg) testimony.assert_match("too short", err) end) -- ========================================================================= -- Decode: existing Phase 1 hand-crafted fixtures -- ========================================================================= testify:that("decode hc_a_simple_response", function() local data = crypto.hex_to_bin(fixtures.hc_a_simple_response) local msg = message.decode(data) local exp = expected.hc_a_simple testimony.assert_equal(0xABCD, msg.header.id) testimony.assert_equal(exp.flags.qr, msg.header.qr) testimony.assert_equal(exp.rcode, msg.header.rcode) testimony.assert_equal(true, msg.header.rd) testimony.assert_equal(true, msg.header.ra) testimony.assert_equal(false, msg.header.aa) testimony.assert_equal(false, msg.header.tc) testimony.assert_equal(1, #msg.question) testimony.assert_equal("example.com.", msg.question[1].name) testimony.assert_equal(1, msg.question[1].qtype) testimony.assert_equal(1, msg.question[1].qclass) testimony.assert_equal(1, #msg.answer) testimony.assert_equal("example.com.", msg.answer[1].name) testimony.assert_equal(1, msg.answer[1].type) testimony.assert_equal(300, msg.answer[1].ttl) testimony.assert_equal("93.184.216.34", msg.answer[1].rdata.address) testimony.assert_equal(0, #msg.authority) testimony.assert_equal(0, #msg.additional) testimony.assert_nil(msg.edns) end) testify:that("decode hc_cname_chain_response", function() local data = crypto.hex_to_bin(fixtures.hc_cname_chain_response) local msg = message.decode(data) testimony.assert_equal(0x1234, msg.header.id) testimony.assert_equal(2, #msg.answer) -- Answer 1: CNAME testimony.assert_equal("www.example.com.", msg.answer[1].name) testimony.assert_equal(types.TYPE.CNAME, msg.answer[1].type) testimony.assert_equal(600, msg.answer[1].ttl) testimony.assert_equal("example.com.", msg.answer[1].rdata.cname) -- Answer 2: A testimony.assert_equal("example.com.", msg.answer[2].name) testimony.assert_equal(types.TYPE.A, msg.answer[2].type) testimony.assert_equal(300, msg.answer[2].ttl) testimony.assert_equal("93.184.216.34", msg.answer[2].rdata.address) end) testify:that("decode hc_multi_answer_response", function() local data = crypto.hex_to_bin(fixtures.hc_multi_answer_response) local msg = message.decode(data) testimony.assert_equal(0x5678, msg.header.id) testimony.assert_equal(3, #msg.answer) testimony.assert_equal("198.51.100.1", msg.answer[1].rdata.address) testimony.assert_equal("198.51.100.2", msg.answer[2].rdata.address) testimony.assert_equal("198.51.100.3", msg.answer[3].rdata.address) end) testify:that("decode hc_nxdomain_response", function() local data = crypto.hex_to_bin(fixtures.hc_nxdomain_response) local msg = message.decode(data) testimony.assert_equal(0x9ABC, msg.header.id) testimony.assert_equal(3, msg.header.rcode) testimony.assert_equal(0, #msg.answer) testimony.assert_equal(1, #msg.authority) local soa = msg.authority[1] testimony.assert_equal("example.com.", soa.name) testimony.assert_equal(types.TYPE.SOA, soa.type) testimony.assert_equal("ns.example.com.", soa.rdata.mname) testimony.assert_equal("admin.example.com.", soa.rdata.rname) testimony.assert_equal(1, soa.rdata.serial) testimony.assert_equal(7200, soa.rdata.refresh) testimony.assert_equal(1800, soa.rdata.retry) testimony.assert_equal(604800, soa.rdata.expire) testimony.assert_equal(300, soa.rdata.minimum) end) -- ========================================================================= -- Decode: Phase 2 RDATA-focused fixtures -- ========================================================================= testify:that("decode hc_aaaa_response", function() local data = crypto.hex_to_bin(fixtures.hc_aaaa_response) local msg = message.decode(data) local exp = expected.hc_aaaa testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(exp.flags.qr, msg.header.qr) testimony.assert_equal(1, #msg.question) testimony.assert_equal(exp.qname, msg.question[1].name) testimony.assert_equal(exp.qtype, msg.question[1].qtype) testimony.assert_equal(1, #msg.answer) local rr = msg.answer[1] testimony.assert_equal(exp.answer[1].name, rr.name) testimony.assert_equal(exp.answer[1].type, rr.type) testimony.assert_equal(exp.answer[1].ttl, rr.ttl) testimony.assert_equal(exp.answer[1].rdata.address, rr.rdata.address) end) testify:that("decode hc_mx_response", function() local data = crypto.hex_to_bin(fixtures.hc_mx_response) local msg = message.decode(data) local exp = expected.hc_mx testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(1, #msg.answer) local rr = msg.answer[1] testimony.assert_equal(exp.answer[1].rdata.preference, rr.rdata.preference) testimony.assert_equal(exp.answer[1].rdata.exchange, rr.rdata.exchange) end) testify:that("decode hc_txt_response", function() local data = crypto.hex_to_bin(fixtures.hc_txt_response) local msg = message.decode(data) local exp = expected.hc_txt testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(1, #msg.answer) local rr = msg.answer[1] testimony.assert_equal(2, #rr.rdata.strings) testimony.assert_equal(exp.answer[1].rdata.strings[1], rr.rdata.strings[1]) testimony.assert_equal(exp.answer[1].rdata.strings[2], rr.rdata.strings[2]) testimony.assert_equal(exp.answer[1].rdata.text, rr.rdata.text) end) testify:that("decode hc_srv_response", function() local data = crypto.hex_to_bin(fixtures.hc_srv_response) local msg = message.decode(data) local exp = expected.hc_srv testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(exp.qname, msg.question[1].name) testimony.assert_equal(1, #msg.answer) local rr = msg.answer[1] testimony.assert_equal(exp.answer[1].rdata.priority, rr.rdata.priority) testimony.assert_equal(exp.answer[1].rdata.weight, rr.rdata.weight) testimony.assert_equal(exp.answer[1].rdata.port, rr.rdata.port) testimony.assert_equal(exp.answer[1].rdata.target, rr.rdata.target) end) testify:that("decode hc_ptr_response", function() local data = crypto.hex_to_bin(fixtures.hc_ptr_response) local msg = message.decode(data) local exp = expected.hc_ptr testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(exp.qname, msg.question[1].name) testimony.assert_equal(1, #msg.answer) testimony.assert_equal(exp.answer[1].rdata.ptrdname, msg.answer[1].rdata.ptrdname) end) testify:that("decode hc_ns_response", function() local data = crypto.hex_to_bin(fixtures.hc_ns_response) local msg = message.decode(data) local exp = expected.hc_ns testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(2, #msg.answer) testimony.assert_equal(exp.answer[1].rdata.nsdname, msg.answer[1].rdata.nsdname) testimony.assert_equal(exp.answer[2].rdata.nsdname, msg.answer[2].rdata.nsdname) testimony.assert_equal(exp.answer[1].ttl, msg.answer[1].ttl) end) testify:that("decode hc_edns_response", function() local data = crypto.hex_to_bin(fixtures.hc_edns_response) local msg = message.decode(data) local exp = expected.hc_edns testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(1, #msg.answer) testimony.assert_equal("93.184.216.34", msg.answer[1].rdata.address) -- EDNS extracted from additional testimony.assert_not_nil(msg.edns) testimony.assert_equal(exp.edns.udp_size, msg.edns.udp_size) testimony.assert_equal(exp.edns.version, msg.edns.version) testimony.assert_equal(exp.edns.do_bit, msg.edns.do_bit) testimony.assert_equal(exp.edns.extended_rcode, msg.edns.extended_rcode) -- OPT removed from additional testimony.assert_equal(0, #msg.additional) end) testify:that("decode hc_soa_response", function() local data = crypto.hex_to_bin(fixtures.hc_soa_response) local msg = message.decode(data) local exp = expected.hc_soa testimony.assert_equal(0xBEEF, msg.header.id) testimony.assert_equal(true, msg.header.aa) testimony.assert_equal(1, #msg.answer) local soa = msg.answer[1] testimony.assert_equal(exp.answer[1].name, soa.name) testimony.assert_equal(exp.answer[1].type, soa.type) testimony.assert_equal(exp.answer[1].ttl, soa.ttl) testimony.assert_equal(exp.answer[1].rdata.mname, soa.rdata.mname) testimony.assert_equal(exp.answer[1].rdata.rname, soa.rdata.rname) testimony.assert_equal(exp.answer[1].rdata.serial, soa.rdata.serial) testimony.assert_equal(exp.answer[1].rdata.refresh, soa.rdata.refresh) testimony.assert_equal(exp.answer[1].rdata.retry, soa.rdata.retry) testimony.assert_equal(exp.answer[1].rdata.expire, soa.rdata.expire) testimony.assert_equal(exp.answer[1].rdata.minimum, soa.rdata.minimum) end) -- ========================================================================= -- Encode: simple query and response -- ========================================================================= testify:that("encode simple A query", function() local msg = { header = { id = 0x1234, qr = 0, opcode = 0, rd = true, rcode = 0 }, question = { { name = "example.com.", qtype = types.TYPE.A, qclass = types.CLASS.IN }, }, } local raw_bytes = message.encode(msg) -- Verify by decoding back local decoded = message.decode(raw_bytes) testimony.assert_equal(0x1234, decoded.header.id) testimony.assert_equal(0, decoded.header.qr) testimony.assert_equal(true, decoded.header.rd) testimony.assert_equal(1, #decoded.question) testimony.assert_equal("example.com.", decoded.question[1].name) testimony.assert_equal(types.TYPE.A, decoded.question[1].qtype) end) testify:that("encode simple A response", function() local msg = { header = { id = 0x1234, qr = 1, rd = true, ra = true, rcode = 0 }, question = { { name = "example.com.", qtype = types.TYPE.A, qclass = types.CLASS.IN }, }, answer = { { name = "example.com.", type = types.TYPE.A, class = types.CLASS.IN, ttl = 300, rdata = { address = "10.0.0.1" }, }, }, } local raw_bytes = message.encode(msg) local decoded = message.decode(raw_bytes) testimony.assert_equal(1, decoded.header.qr) testimony.assert_equal(1, #decoded.answer) testimony.assert_equal("10.0.0.1", decoded.answer[1].rdata.address) testimony.assert_equal(300, decoded.answer[1].ttl) end) -- ========================================================================= -- Round-trip: decode fixture → encode → decode → compare -- ========================================================================= testify:that("round-trip hc_a_simple_response", function() local data = crypto.hex_to_bin(fixtures.hc_a_simple_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.header.id, msg2.header.id) testimony.assert_equal(msg1.header.qr, msg2.header.qr) testimony.assert_equal(msg1.header.rcode, msg2.header.rcode) testimony.assert_equal(#msg1.question, #msg2.question) testimony.assert_equal(msg1.question[1].name, msg2.question[1].name) testimony.assert_equal(#msg1.answer, #msg2.answer) testimony.assert_equal(msg1.answer[1].rdata.address, msg2.answer[1].rdata.address) end) testify:that("round-trip hc_cname_chain_response", function() local data = crypto.hex_to_bin(fixtures.hc_cname_chain_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.header.id, msg2.header.id) testimony.assert_equal(2, #msg2.answer) testimony.assert_equal(msg1.answer[1].rdata.cname, msg2.answer[1].rdata.cname) testimony.assert_equal(msg1.answer[2].rdata.address, msg2.answer[2].rdata.address) end) testify:that("round-trip hc_nxdomain_response with SOA", function() local data = crypto.hex_to_bin(fixtures.hc_nxdomain_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(3, msg2.header.rcode) testimony.assert_equal(1, #msg2.authority) testimony.assert_equal(msg1.authority[1].rdata.mname, msg2.authority[1].rdata.mname) testimony.assert_equal(msg1.authority[1].rdata.serial, msg2.authority[1].rdata.serial) end) testify:that("round-trip hc_aaaa_response", function() local data = crypto.hex_to_bin(fixtures.hc_aaaa_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.answer[1].rdata.address, msg2.answer[1].rdata.address) end) testify:that("round-trip hc_mx_response", function() local data = crypto.hex_to_bin(fixtures.hc_mx_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.answer[1].rdata.preference, msg2.answer[1].rdata.preference) testimony.assert_equal(msg1.answer[1].rdata.exchange, msg2.answer[1].rdata.exchange) end) testify:that("round-trip hc_txt_response", function() local data = crypto.hex_to_bin(fixtures.hc_txt_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(#msg1.answer[1].rdata.strings, #msg2.answer[1].rdata.strings) testimony.assert_equal(msg1.answer[1].rdata.text, msg2.answer[1].rdata.text) end) testify:that("round-trip hc_srv_response", function() local data = crypto.hex_to_bin(fixtures.hc_srv_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.answer[1].rdata.priority, msg2.answer[1].rdata.priority) testimony.assert_equal(msg1.answer[1].rdata.weight, msg2.answer[1].rdata.weight) testimony.assert_equal(msg1.answer[1].rdata.port, msg2.answer[1].rdata.port) testimony.assert_equal(msg1.answer[1].rdata.target, msg2.answer[1].rdata.target) end) testify:that("round-trip hc_ns_response", function() local data = crypto.hex_to_bin(fixtures.hc_ns_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(2, #msg2.answer) testimony.assert_equal(msg1.answer[1].rdata.nsdname, msg2.answer[1].rdata.nsdname) testimony.assert_equal(msg1.answer[2].rdata.nsdname, msg2.answer[2].rdata.nsdname) end) testify:that("round-trip hc_soa_response", function() local data = crypto.hex_to_bin(fixtures.hc_soa_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_equal(msg1.answer[1].rdata.mname, msg2.answer[1].rdata.mname) testimony.assert_equal(msg1.answer[1].rdata.rname, msg2.answer[1].rdata.rname) testimony.assert_equal(msg1.answer[1].rdata.serial, msg2.answer[1].rdata.serial) testimony.assert_equal(msg1.answer[1].rdata.refresh, msg2.answer[1].rdata.refresh) testimony.assert_equal(msg1.answer[1].rdata.retry, msg2.answer[1].rdata.retry) testimony.assert_equal(msg1.answer[1].rdata.expire, msg2.answer[1].rdata.expire) testimony.assert_equal(msg1.answer[1].rdata.minimum, msg2.answer[1].rdata.minimum) end) -- ========================================================================= -- EDNS -- ========================================================================= testify:that("encode with edns produces OPT record", function() local msg = { header = { id = 0xAAAA, qr = 0, rd = true, rcode = 0 }, question = { { name = "example.com.", qtype = types.TYPE.A, qclass = types.CLASS.IN }, }, edns = { udp_size = 4096, version = 0, do_bit = false, extended_rcode = 0, options = {}, }, } local raw_bytes = message.encode(msg) local decoded = message.decode(raw_bytes) testimony.assert_not_nil(decoded.edns) testimony.assert_equal(4096, decoded.edns.udp_size) testimony.assert_equal(0, decoded.edns.version) testimony.assert_equal(false, decoded.edns.do_bit) end) testify:that("encode with DO bit set", function() local msg = { header = { id = 0xBBBB, qr = 0, rd = true, rcode = 0 }, question = { { name = "example.com.", qtype = types.TYPE.A, qclass = types.CLASS.IN }, }, edns = { udp_size = 4096, version = 0, do_bit = true, extended_rcode = 0, options = {}, }, } local raw_bytes = message.encode(msg) local decoded = message.decode(raw_bytes) testimony.assert_not_nil(decoded.edns) testimony.assert_equal(true, decoded.edns.do_bit) end) testify:that("round-trip hc_edns_response preserves edns", function() local data = crypto.hex_to_bin(fixtures.hc_edns_response) local msg1 = message.decode(data) local reencoded = message.encode(msg1) local msg2 = message.decode(reencoded) testimony.assert_not_nil(msg2.edns) testimony.assert_equal(msg1.edns.udp_size, msg2.edns.udp_size) testimony.assert_equal(msg1.edns.version, msg2.edns.version) testimony.assert_equal(msg1.edns.do_bit, msg2.edns.do_bit) testimony.assert_equal(0, #msg2.additional) end) -- ========================================================================= -- Unknown type -- ========================================================================= testify:that("unknown type preserves raw bytes through roundtrip", function() -- Build a message with an unknown RR type (65534) local msg = { header = { id = 0xDEAD, qr = 1, rd = true, ra = true, rcode = 0 }, question = { { name = "example.com.", qtype = 65534, qclass = types.CLASS.IN }, }, answer = { { name = "example.com.", type = 65534, class = types.CLASS.IN, ttl = 60, rdata = { raw = "\xDE\xAD\xBE\xEF" }, }, }, } local raw_bytes = message.encode(msg) local decoded = message.decode(raw_bytes) testimony.assert_equal(1, #decoded.answer) testimony.assert_equal(65534, decoded.answer[1].type) testimony.assert_not_nil(decoded.answer[1].rdata.raw) testimony.assert_equal(4, #decoded.answer[1].rdata.raw) testimony.assert_equal(msg.answer[1].rdata.raw, decoded.answer[1].rdata.raw) end) -- ========================================================================= -- Multi-section message -- ========================================================================= testify:that("encode/decode message with all sections", function() local msg = { header = { id = 0x4321, qr = 1, aa = true, rd = true, ra = true, rcode = 0 }, question = { { name = "example.com.", qtype = types.TYPE.A, qclass = types.CLASS.IN }, }, answer = { { name = "example.com.", type = types.TYPE.A, class = types.CLASS.IN, ttl = 300, rdata = { address = "1.2.3.4" }, }, }, authority = { { name = "example.com.", type = types.TYPE.NS, class = types.CLASS.IN, ttl = 3600, rdata = { nsdname = "ns1.example.com." }, }, }, additional = { { name = "ns1.example.com.", type = types.TYPE.A, class = types.CLASS.IN, ttl = 3600, rdata = { address = "5.6.7.8" }, }, }, } local raw_bytes = message.encode(msg) local decoded = message.decode(raw_bytes) testimony.assert_equal(1, #decoded.question) testimony.assert_equal(1, #decoded.answer) testimony.assert_equal(1, #decoded.authority) testimony.assert_equal(1, #decoded.additional) testimony.assert_equal("1.2.3.4", decoded.answer[1].rdata.address) testimony.assert_equal("ns1.example.com.", decoded.authority[1].rdata.nsdname) testimony.assert_equal("5.6.7.8", decoded.additional[1].rdata.address) end) -- ========================================================================= -- Captured real-world fixtures (Phase 2: deviant.guru RDATA tests) -- These tests only run if capture_fixtures.bash has populated the entries. -- They are skipped (not failed) when the captured hex strings are empty. -- ========================================================================= -- Helper: skip test if fixture is empty (not captured yet) local function have_fixture(key) return fixtures[key] and fixtures[key] ~= "" end if have_fixture("aaaa_deviant_response") then testify:that("captured AAAA deviant.guru decodes", function() local data = crypto.hex_to_bin(fixtures.aaaa_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(1, msg.header.qr) testimony.assert_equal(1, #msg.question) testimony.assert_equal(28, msg.question[1].qtype) -- AAAA if #msg.answer > 0 then testimony.assert_not_nil(msg.answer[1].rdata.address) -- Verify AAAA RDATA round-trips local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_equal(msg.answer[1].rdata.address, msg2.answer[1].rdata.address) end end) end if have_fixture("mx_deviant_response") then testify:that("captured MX deviant.guru decodes", function() local data = crypto.hex_to_bin(fixtures.mx_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(15, msg.question[1].qtype) -- MX if #msg.answer > 0 then testimony.assert_not_nil(msg.answer[1].rdata.preference) testimony.assert_not_nil(msg.answer[1].rdata.exchange) -- Round-trip local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_equal(msg.answer[1].rdata.preference, msg2.answer[1].rdata.preference) testimony.assert_equal(msg.answer[1].rdata.exchange, msg2.answer[1].rdata.exchange) end end) end if have_fixture("ns_deviant_response") then testify:that("captured NS deviant.guru decodes", function() local data = crypto.hex_to_bin(fixtures.ns_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(2, msg.question[1].qtype) -- NS if #msg.answer > 0 then testimony.assert_not_nil(msg.answer[1].rdata.nsdname) -- Round-trip local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_equal(msg.answer[1].rdata.nsdname, msg2.answer[1].rdata.nsdname) end end) end if have_fixture("txt_deviant_response") then testify:that("captured TXT deviant.guru decodes", function() local data = crypto.hex_to_bin(fixtures.txt_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(16, msg.question[1].qtype) -- TXT if #msg.answer > 0 then testimony.assert_not_nil(msg.answer[1].rdata.strings) testimony.assert_not_nil(msg.answer[1].rdata.text) testimony.assert_true(#msg.answer[1].rdata.strings > 0) -- Round-trip local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_equal(msg.answer[1].rdata.text, msg2.answer[1].rdata.text) end end) end if have_fixture("soa_deviant_response") then testify:that("captured SOA deviant.guru decodes", function() local data = crypto.hex_to_bin(fixtures.soa_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(6, msg.question[1].qtype) -- SOA -- SOA might be in answer or authority section local soa_rr = nil if #msg.answer > 0 and msg.answer[1].type == types.TYPE.SOA then soa_rr = msg.answer[1] elseif #msg.authority > 0 and msg.authority[1].type == types.TYPE.SOA then soa_rr = msg.authority[1] end if soa_rr then testimony.assert_not_nil(soa_rr.rdata.mname) testimony.assert_not_nil(soa_rr.rdata.rname) testimony.assert_not_nil(soa_rr.rdata.serial) -- Round-trip local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) local soa2 = #msg2.answer > 0 and msg2.answer[1] or msg2.authority[1] testimony.assert_equal(soa_rr.rdata.mname, soa2.rdata.mname) testimony.assert_equal(soa_rr.rdata.serial, soa2.rdata.serial) end end) end if have_fixture("ptr_one_response") then testify:that("captured PTR 1.1.1.1 decodes", function() local data = crypto.hex_to_bin(fixtures.ptr_one_response) local msg = message.decode(data) testimony.assert_not_nil(msg) testimony.assert_equal(12, msg.question[1].qtype) -- PTR if #msg.answer > 0 then testimony.assert_not_nil(msg.answer[1].rdata.ptrdname) -- Round-trip local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_equal(msg.answer[1].rdata.ptrdname, msg2.answer[1].rdata.ptrdname) end end) end if have_fixture("edns_deviant_response") then testify:that("captured EDNS deviant.guru decodes with OPT", function() local data = crypto.hex_to_bin(fixtures.edns_deviant_response) local msg = message.decode(data) testimony.assert_not_nil(msg) -- EDNS response should have edns field populated if msg.edns then testimony.assert_not_nil(msg.edns.udp_size) testimony.assert_not_nil(msg.edns.version) -- OPT should be removed from additional for _, rr in ipairs(msg.additional) do testimony.assert_false(rr.type == types.TYPE.OPT) end -- Round-trip preserves EDNS local reencoded = message.encode(msg) local msg2 = message.decode(reencoded) testimony.assert_not_nil(msg2.edns) testimony.assert_equal(msg.edns.udp_size, msg2.edns.udp_size) end end) end testify:conclude()