// DNS Question section (RFC 1035 ยง4.1.2).
///|
pub struct Question {
name : String
qtype : UInt16
qclass : UInt16
}
// Encode a single question to wire format. Message::encode uses the internal
// compression writer; this method is the standalone, uncompressed form.
///|
pub fn Question::encode_checked(self : Question) -> Result[Array[Byte], String] {
let name_bytes = match encode_name_checked(self.name) {
Ok(bytes) => bytes
Err(err) => return Err("DNS question name: " + err)
}
let buf = Array::make(name_bytes.length() + 4, (0).to_byte())
let pos = Ref(0)
for i = 0; i < name_bytes.length(); i = i + 1 {
buf[pos.val] = name_bytes[i]
pos.val = pos.val + 1
}
buf[pos.val] = ((self.qtype >> 8) & 0xFF).to_byte()
buf[pos.val + 1] = (self.qtype & 0xFF).to_byte()
buf[pos.val + 2] = ((self.qclass >> 8) & 0xFF).to_byte()
buf[pos.val + 3] = (self.qclass & 0xFF).to_byte()
Ok(buf)
}
///|
pub fn Question::encode(self : Question) -> Array[Byte] {
match self.encode_checked() {
Ok(bytes) => bytes
Err(error) => abort(error)
}
}
// Decode a single question from wire format.
///|
pub fn decode_question(
bytes : Array[Byte],
offset : Int,
msg_start : Int,
) -> Result[(Question, Int), String] {
let (name, o1) = match decode_name(bytes, offset, msg_start) {
Ok(value) => value
Err(err) => return Err("DNS question name: " + err)
}
match wire_check_range(bytes, o1, 4) {
Err(err) => return Err("DNS question: " + err)
Ok(_) => ()
}
let qtype = ((bytes[o1].to_int() << 8) | bytes[o1 + 1].to_int()).to_uint16()
let qclass = ((bytes[o1 + 2].to_int() << 8) | bytes[o1 + 3].to_int()).to_uint16()
Ok(({ name, qtype, qclass }, o1 + 4))
}
// Decode multiple questions in sequence.
///|
pub fn decode_questions(
bytes : Array[Byte],
offset : Int,
count : UInt16,
msg_start : Int,
) -> Result[(Array[Question], Int), String] {
if count.to_int() > max_section_records {
return Err("DNS question count exceeds codec limit")
}
let questions : Array[Question] = Array::new(capacity=count.to_int())
let pos = Ref(offset)
for _i in 0.. value
Err(err) => return Err(err)
}
questions.push(q)
pos.val = next
}
Ok((questions, pos.val))
}
// Create a question for an A record query
///|
pub fn question_a(name : String) -> Question {
{ name, qtype: qtype_a, qclass: qclass_in }
}
// Create a question for an AAAA record query
///|
pub fn question_aaaa(name : String) -> Question {
{ name, qtype: qtype_aaaa, qclass: qclass_in }
}
// Create a question for an MX record query
///|
pub fn question_mx(name : String) -> Question {
{ name, qtype: qtype_mx, qclass: qclass_in }
}
// Create a question for a NS record query
///|
pub fn question_ns(name : String) -> Question {
{ name, qtype: qtype_ns, qclass: qclass_in }
}
// Create a question for a CNAME record query
///|
pub fn question_cname(name : String) -> Question {
{ name, qtype: qtype_cname, qclass: qclass_in }
}
// Create a question for a TXT record query
///|
pub fn question_txt(name : String) -> Question {
{ name, qtype: qtype_txt, qclass: qclass_in }
}
// Create a question for a SOA record query
///|
pub fn question_soa(name : String) -> Question {
{ name, qtype: qtype_soa, qclass: qclass_in }
}
// Create a question for a PTR record query
///|
pub fn question_ptr(name : String) -> Question {
{ name, qtype: qtype_ptr, qclass: qclass_in }
}
// Create a question for an SRV record query
///|
pub fn question_srv(name : String) -> Question {
{ name, qtype: qtype_srv, qclass: qclass_in }
}
// Create a question for an ANY record query
///|
pub fn question_any(name : String) -> Question {
{ name, qtype: qtype_any, qclass: qclass_in }
}
// Create a question with custom type and class
///|
pub fn question_custom(
name : String,
qtype : UInt16,
qclass? : UInt16 = qclass_in,
) -> Question {
{ name, qtype, qclass }
}
// Check if a question is for a specific record type
///|
pub fn question_is_type(q : Question, qtype : UInt16) -> Bool {
q.qtype == qtype
}
// Check if a question uses the standard IN class
///|
pub fn question_is_internet(q : Question) -> Bool {
q.qclass == qclass_in
}
// Get a human-readable description of a question
///|
pub fn question_description(q : Question) -> String {
q.name + " IN " + qtype_to_string(q.qtype)
}
// Get the expected wire format size of a question
///|
pub fn question_wire_size(q : Question) -> Int {
name_wire_size(q.name) + 4
}
// Build a batch of questions for multiple record types for the same name
///|
pub fn questions_for_types(
name : String,
qtypes : Array[UInt16],
) -> Array[Question] {
let result : Array[Question] = Array::new(capacity=qtypes.length())
for i = 0; i < qtypes.length(); i = i + 1 {
result.push(question_custom(name, qtypes[i]))
}
result
}
// Encode multiple questions into a single byte array
///|
pub fn encode_questions(questions : Array[Question]) -> Array[Byte] {
let total = Ref(0)
for q in questions {
total.val = total.val + question_wire_size(q)
}
let buf = Array::make(total.val, (0).to_byte())
let pos = Ref(0)
for q in questions {
let q_bytes = q.encode()
for i = 0; i < q_bytes.length(); i = i + 1 {
buf[pos.val] = q_bytes[i]
pos.val = pos.val + 1
}
}
buf
}
// Clone a question (useful for building responses)
///|
pub fn question_clone(q : Question) -> Question {
{ name: q.name, qtype: q.qtype, qclass: q.qclass }
}