///|
fn append(out : Array[Byte], bytes : Bytes) -> Unit {
for b in bytes {
out.push(b)
}
}
///|
fn put16(out : Array[Byte], n : Int32) -> Unit {
out.push((n >> 8).to_byte())
out.push((n & 255).to_byte())
}
///|
fn payload(record : Record) -> Bytes raise WireError {
match (record.rr_type, record.data) {
(1, A(bytes)) => {
guard bytes.length() == 4 else {
raise Malformed("invalid A known answer")
}
bytes
}
(28, Aaaa(bytes)) => {
guard bytes.length() == 16 else {
raise Malformed("invalid AAAA known answer")
}
bytes
}
(12, Ptr(name)) => name.to_wire()
(16, Txt(txt)) => txt.to_wire()
(33, Srv(priority, weight, port, target)) => {
guard priority >= 0 &&
priority <= 65535 &&
weight >= 0 &&
weight <= 65535 &&
port >= 0 &&
port <= 65535 else {
raise Malformed("SRV known answer fields outside wire range")
}
let bytes : Array[Byte] = []
put16(bytes, priority)
put16(bytes, weight)
put16(bytes, port)
append(bytes, target.to_wire())
Bytes::makei(bytes.length(), i => bytes[i])
}
_ => raise Malformed("unsupported/mismatched known answer")
}
}
///|
/// A single bounded QM query. Oversized Known-Answer sets return an error;
/// multi-packet KA is deliberately not approximated by silent truncation.
pub fn query_with_answers(
name : @model.DnsName,
rr_type : Int32,
answers : Array[Record],
) -> Bytes raise WireError {
guard answers.length() <= 255 else {
raise Malformed("known answer count exceeds 255")
}
let out = qm_query(name, rr_type).to_array()
out[6] = (answers.length() >> 8).to_byte()
out[7] = (answers.length() & 255).to_byte()
for record in answers {
guard record.rr_class == 1 &&
record.rr_type == rr_type &&
record.name.same_name(name) else {
raise Malformed("known answer does not match question")
}
let bytes = payload(record)
guard bytes.length() <= 65535 else {
raise Malformed("RDATA exceeds wire length")
}
append(out, record.name.to_wire())
put16(out, record.rr_type)
put16(out, 1) // cache-flush MUST NOT appear in a query's Known Answers.
out.push((record.ttl >> 24).to_byte())
out.push(((record.ttl >> 16) & 255U).to_byte())
out.push(((record.ttl >> 8) & 255U).to_byte())
out.push((record.ttl & 255U).to_byte())
put16(out, bytes.length())
append(out, bytes)
guard out.length() <= 9000 else {
raise Malformed("single-packet known answer limit (9000 octets)")
}
}
Bytes::makei(out.length(), i => out[i])
}
///|
fn known_record(
name : @model.DnsName,
rr_type : Int32,
record : Record,
) -> Bytes raise WireError {
guard record.rr_class == 1 &&
record.rr_type == rr_type &&
record.name.same_name(name) else {
raise Malformed("known answer does not match question")
}
let data = payload(record)
guard data.length() <= 65535 else {
raise Malformed("RDATA exceeds wire length")
}
let out : Array[Byte] = []
append(out, record.name.to_wire())
put16(out, record.rr_type)
put16(out, 1)
out.push((record.ttl >> 24).to_byte())
out.push(((record.ttl >> 16) & 255U).to_byte())
out.push(((record.ttl >> 8) & 255U).to_byte())
out.push((record.ttl & 255U).to_byte())
put16(out, data.length())
append(out, data)
Bytes::makei(out.length(), i => out[i])
}
///|
/// RFC 6762 7.2: question in the first packet, answer-only continuations,
/// TC on every packet except the last. Caller sends the returned burst together.
/// At most `max_packets * 256 - 1` answers (16383 by default); byte limits
/// can reduce this capacity. An error never returns a partial burst.
pub fn query_packets(
name : @model.DnsName,
rr_type : Int32,
answers : Array[Record],
max_size~ : Int32 = 9000,
max_packets~ : Int32 = 64,
) -> Array[Bytes] raise WireError {
guard max_size >= 512 &&
max_size <= 9000 &&
max_packets > 0 &&
max_packets <= 64 else {
raise Malformed("invalid known-answer burst bounds")
}
guard answers.length() <= max_packets * 256 - 1 else {
raise Malformed("known-answer count exceeds packet budget")
}
let result : Array[Bytes] = []
let mut out = qm_query(name, rr_type).to_array()
let mut count = 0
let mut capacity = 255
fn finish(bytes : Array[Byte], records : Int32, more : Bool) -> Bytes {
bytes[2] = if more { b'\x02' } else { b'\x00' }
bytes[6] = (records >> 8).to_byte()
bytes[7] = (records & 255).to_byte()
Bytes::makei(bytes.length(), i => bytes[i])
}
for record in answers {
let data = known_record(name, rr_type, record)
if out.length() + data.length() > max_size || count >= capacity {
guard count > 0 else {
raise Malformed("one known answer exceeds packet size")
}
guard result.length() + 1 < max_packets else {
raise Malformed("known-answer burst exceeds configured bounds")
}
result.push(finish(out, count, true))
out = Array::make(12, b'\x00')
count = 0
capacity = 256
}
guard out.length() + data.length() <= max_size else {
raise Malformed("one known answer exceeds packet size")
}
append(out, data)
count += 1
}
result.push(finish(out, count, false))
result
}