dns-server 0.1.0

A threaded DNS server with no `unsafe`
Documentation
use crate::{read_u16_be, read_u8, write_u16_be, DnsError, DnsOpCode, DnsResponseCode};
use fixed_buffer::FixedBuf;

/// > 4.1.1. Header section format
/// >
/// > The header contains the following fields:
/// >
/// > ```text
/// >                                 1  1  1  1  1  1
/// >   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |                      ID                       |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |                    QDCOUNT                    |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |                    ANCOUNT                    |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |                    NSCOUNT                    |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > |                    ARCOUNT                    |
/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/// > ```
///
/// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DnsMessageHeader {
    /// > `ID` A 16 bit identifier assigned by the program that generates any kind of query.  This
    /// > identifier is copied the corresponding reply and can be used by the requester to match up
    /// > replies to outstanding queries.
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub id: u16,
    /// > `QR` A one bit field that specifies whether this message is a query (`0`),
    /// > or a response (`1`).
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub is_response: bool,
    /// > `OPCODE`  A four bit field that specifies kind of query in this message.
    /// >         This value is set by the originator of a query and copied into
    /// >         the response.  The values are:
    /// > - `0` a standard query (`QUERY`)
    /// > - `1` an inverse query (`IQUERY`)
    /// > - `2` a server status request (`STATUS`)
    /// > - `3-15` reserved for future use
    ///
    /// https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1
    pub op_code: DnsOpCode,
    /// > `AA` Authoritative Answer - this bit is valid in responses, and specifies that the
    /// > responding name server is an authority for the domain name in question section.
    /// >
    /// > Note that the contents of the answer section may have multiple owner names because of
    /// > aliases.  The AA bit corresponds to the name which matches the query name, or the first
    /// > owner name in the answer section.
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub authoritative_answer: bool,
    /// > `TC` TrunCation - specifies that this message was truncated due to length greater than
    /// > that permitted on the transmission channel.
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub truncated: bool,
    /// > `RD` Recursion Desired - this bit may be set in a query and is copied into the response.
    /// > If RD is set, it directs the name server to pursue the query recursively.  Recursive query
    /// > support is optional.
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub recursion_desired: bool,
    /// > `RA` Recursion Available - this be is set or cleared in a response, and denotes whether
    /// > recursive query support is available in the name server.
    ///
    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1>
    pub recursion_available: bool,
    pub response_code: DnsResponseCode,
    pub question_count: u16,
    pub answer_count: u16,
    pub name_server_count: u16,
    pub additional_count: u16,
}
impl DnsMessageHeader {
    /// # Errors
    /// Returns an error when `buf` does not contain a valid message header.
    pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<Self, DnsError> {
        let id = read_u16_be(buf)?;
        let b = read_u8(buf)?;
        let is_response = (b >> 7) == 1;
        let op_code = DnsOpCode::new((b >> 3) & 0xF);
        let authoritative_answer = ((b >> 2) & 1) == 1;
        let truncated = ((b >> 1) & 1) == 1;
        let recursion_desired = (b & 1) == 1;
        let b = read_u8(buf)?;
        let recursion_available = (b >> 7) == 1;
        let response_code = DnsResponseCode::new(b & 0xF);
        let question_count = read_u16_be(buf)?;
        let answer_count = read_u16_be(buf)?;
        let name_server_count = read_u16_be(buf)?;
        let additional_count = read_u16_be(buf)?;
        Ok(Self {
            id,
            is_response,
            op_code,
            authoritative_answer,
            truncated,
            recursion_desired,
            recursion_available,
            response_code,
            question_count,
            answer_count,
            name_server_count,
            additional_count,
        })
    }

    /// # Errors
    /// Returns an error when `buf` fills up.
    pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
        let bytes: [u8; 2] = self.id.to_be_bytes();
        out.write_bytes(&bytes)
            .map_err(|_| DnsError::ResponseBufferFull)?;
        let b = (u8::from(self.is_response) << 7)
            | (self.op_code.num() << 3)
            | (u8::from(self.authoritative_answer) << 2)
            | (u8::from(self.truncated) << 1)
            | u8::from(self.recursion_desired);
        out.write_bytes(&[b])
            .map_err(|_| DnsError::ResponseBufferFull)?;
        let b = (u8::from(self.recursion_available) << 7) | self.response_code.num();
        out.write_bytes(&[b])
            .map_err(|_| DnsError::ResponseBufferFull)?;
        for count in [
            self.question_count,
            self.answer_count,
            self.name_server_count,
            self.additional_count,
        ] {
            write_u16_be(out, count)?;
        }
        Ok(())
    }
}