Skip to main content

pharos_plugins/
minimal.rs

1//! Remove unnecessary sections from positive Responses.
2
3use pharos_proto::{Message, NameRef, NameWirePart, ResponseType, typify_response};
4use pharos_server::{Context, Next, Outcome, Plugin};
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug)]
8pub struct Minimal;
9
10fn references_counts(name: NameRef<'_>) -> bool {
11    name.wire_parts().any(|part| match part {
12        NameWirePart::Label { offset, len } => offset < 12 && offset + len > 8,
13        NameWirePart::Root { offset } | NameWirePart::Pointer { offset, .. } => {
14            (8..12).contains(&offset)
15        }
16    })
17}
18
19impl Plugin for Minimal {
20    async fn serve<N: Next + Send>(&self, cx: &mut Context<'_>, next: N) -> Outcome {
21        let outcome = next.run(cx).await;
22        if outcome != Outcome::Response {
23            return outcome;
24        }
25        let Ok(message) = Message::parse(cx.response) else {
26            return outcome;
27        };
28        let now = SystemTime::now()
29            .duration_since(UNIX_EPOCH)
30            .map_or(0, |time| time.as_secs() as i64);
31        if typify_response(Some(&message), now) != ResponseType::Positive {
32            return outcome;
33        }
34        // RFC 1035 §4.1.4: changing header counts must not alter compressed names.
35        // shortcut: retain Responses with header-count compression references or malformed compressible Answer RDATA, re-encode them without allocation to minimize.
36        if message
37            .questions()
38            .any(|question| references_counts(question.name))
39            || message.answers().any(|record| {
40                references_counts(record.name)
41                    || record.compressible_names().map_or(true, |names| {
42                        names.into_iter().flatten().any(references_counts)
43                    })
44            })
45        {
46            return outcome;
47        }
48        // RFC 1035 §4.1.1: section counts describe the records in each Message section.
49        let end = message
50            .authorities()
51            .next()
52            .or_else(|| message.additionals().next())
53            .map_or(cx.response.len(), |record| record.name.offset());
54        cx.response.truncate(end);
55        cx.response[8..12].fill(0);
56        outcome
57    }
58}