1use std::{collections::HashMap, net::SocketAddr};
2
3use pharos_proto::{Message, Name, NameRef, Opcode, Type};
4
5use crate::{
6 Chain, ListenerKey, Protocol,
7 metrics::{BlockMetrics, PluginMetrics, Registry},
8};
9
10pub type ViewFilter = fn(&Message<'_>, SocketAddr, Protocol) -> bool;
12
13pub struct ServerBlock<C> {
15 domain: Name,
16 chain: Option<C>,
17 filters: Vec<ViewFilter>,
18 metrics: Option<BlockMetrics>,
19 plugin_metrics: Vec<(&'static str, PluginMetrics)>,
20 plugin_names: Vec<&'static str>,
21}
22
23impl<C> ServerBlock<C> {
24 pub fn domain(&self) -> &Name {
25 &self.domain
26 }
27 pub fn chain(&self) -> Option<&C> {
28 self.chain.as_ref()
29 }
30 pub(crate) fn metrics(&self) -> Option<&BlockMetrics> {
31 self.metrics.as_ref()
32 }
33 pub(crate) fn plugin_metrics(&self, name: &str) -> Option<&PluginMetrics> {
34 self.plugin_metrics
35 .iter()
36 .find(|(candidate, _)| *candidate == name)
37 .map(|(_, metrics)| metrics)
38 }
39 pub(crate) fn plugin_name(&self, name: &str) -> Option<&'static str> {
40 self.plugin_names
41 .iter()
42 .copied()
43 .find(|candidate| *candidate == name)
44 }
45}
46
47pub struct Router<C> {
49 listeners: HashMap<ListenerKey, Vec<ServerBlock<C>>>,
50 metric_contexts: HashMap<ListenerKey, MetricContext<C>>,
51}
52
53struct MetricContext<C> {
54 registry: Registry,
55 server: String,
56 prepare: fn(&mut ServerBlock<C>, &Registry, &str),
57}
58
59impl<C> Default for Router<C> {
60 fn default() -> Self {
61 Self {
62 listeners: HashMap::new(),
63 metric_contexts: HashMap::new(),
64 }
65 }
66}
67
68impl<C> Router<C> {
69 pub fn add(
70 &mut self,
71 key: ListenerKey,
72 domain: Name,
73 chain: Option<C>,
74 filters: Vec<ViewFilter>,
75 ) {
76 let mut block = ServerBlock {
77 domain,
78 chain,
79 filters,
80 metrics: None,
81 plugin_metrics: Vec::new(),
82 plugin_names: Vec::new(),
83 };
84 if let Some(context) = self.metric_contexts.get(&key) {
85 (context.prepare)(&mut block, &context.registry, &context.server);
86 }
87 let blocks = self.listeners.entry(key).or_default();
88 blocks.push(block);
89 blocks.sort_by_key(|block| std::cmp::Reverse(block.domain.label_count()));
90 }
91
92 pub fn route(
94 &self,
95 key: &ListenerKey,
96 query: Message<'_>,
97 peer: SocketAddr,
98 protocol: Protocol,
99 ) -> Option<&ServerBlock<C>> {
100 let question = query.questions().next()?;
101 let blocks = self.listeners.get(key)?;
102 let ds = query.opcode() == Opcode::QUERY && question.qtype == Type::DS;
103 let labels = question.name.labels().count();
104 let mut selected = None;
105 for block in blocks
106 .iter()
107 .filter(|block| block.domain.label_count() != 0)
108 {
109 let count = block.domain.label_count();
110 if count > labels {
111 continue;
112 }
113 let domain = NameRef::parse(block.domain.as_wire(), 0).expect("validated Domain");
114 if !question
116 .name
117 .labels()
118 .skip(labels - count)
119 .zip(domain.labels())
120 .all(|(a, b)| a.eq_ignore_ascii_case(b))
121 {
122 continue;
123 }
124 block.chain.as_ref()?;
125 if block
126 .filters
127 .iter()
128 .all(|filter| filter(&query, peer, protocol))
129 {
130 if !ds {
131 return Some(block);
132 }
133 selected = Some(block);
134 }
135 }
136 if selected.is_some() {
137 return selected;
138 }
139 blocks
140 .iter()
141 .filter(|block| block.domain.label_count() == 0 && block.chain.is_some())
142 .find(|block| {
143 block
144 .filters
145 .iter()
146 .all(|filter| filter(&query, peer, protocol))
147 })
148 }
149}
150
151impl<C: Chain> Router<C> {
152 pub(crate) fn metrics_enabled(&self, key: &ListenerKey) -> bool {
153 self.listeners.get(key).is_some_and(|blocks| {
154 blocks
155 .iter()
156 .any(|block| block.chain.as_ref().is_some_and(Chain::metrics_enabled))
157 })
158 }
159 pub(crate) fn prepare_metrics(&mut self, key: &ListenerKey, registry: &Registry, server: &str) {
160 self.metric_contexts.insert(
161 *key,
162 MetricContext {
163 registry: registry.clone(),
164 server: server.to_owned(),
165 prepare: prepare_block::<C>,
166 },
167 );
168 if let Some(blocks) = self.listeners.get_mut(key) {
169 for block in blocks {
170 prepare_block(block, registry, server);
171 }
172 }
173 }
174}
175
176fn prepare_block<C: Chain>(block: &mut ServerBlock<C>, registry: &Registry, server: &str) {
177 block.plugin_metrics.clear();
179 block.plugin_names.clear();
180 if let Some(chain) = &block.chain {
181 if chain.stacktrace() {
182 crate::panic::prepare_stacktrace();
183 }
184 let metrics = chain
185 .metrics_enabled()
186 .then(|| registry.block(server, &block.domain.to_string(), ""));
187 chain.visit_enabled(&mut |name| {
188 block.plugin_names.push(name);
189 if let Some(metrics) = &metrics {
190 metrics.plugin_enabled(name);
191 block.plugin_metrics.push((name, metrics.plugin(name)));
192 }
193 });
194 block.metrics = metrics;
195 }
196}