1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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(())
    }
}