1use crate::rr::draft_ietf_dnsop_svcb_https::ServiceBindingMode::{Alias, Service};
2use crate::rr::{ToType, Type};
3use crate::DomainName;
4use base64::{engine::general_purpose::STANDARD as Base64Standard, Engine};
5use std::cmp::Ordering;
6use std::collections::BTreeSet;
7use std::fmt::{Display, Formatter, Result as FmtResult};
8use std::hash::{Hash, Hasher};
9use std::net::{Ipv4Addr, Ipv6Addr};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct ServiceBinding {
15 pub name: DomainName,
16 pub ttl: u32,
17
18 pub priority: u16,
23 pub target_name: DomainName,
24 pub parameters: BTreeSet<ServiceParameter>,
25 pub https: bool,
27}
28
29impl ToType for ServiceBinding {
30 fn to_type(&self) -> Type {
31 if self.https {
32 Type::HTTPS
33 } else {
34 Type::SVCB
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum ServiceBindingMode {
42 Alias,
45
46 Service,
49}
50
51impl Display for ServiceBinding {
52 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
53 let record_type = if self.https { "HTTPS" } else { "SVCB" };
54 write!(
55 f,
56 "{} {} IN {} {} {}",
57 self.name, self.ttl, record_type, self.priority, self.target_name
58 )?;
59 self.parameters
60 .iter()
61 .try_for_each(|parameter| -> FmtResult {
62 write!(f, " ")?;
63 parameter.fmt(f)
64 })
65 }
66}
67
68impl ServiceBinding {
69 pub fn mode(&self) -> ServiceBindingMode {
70 if self.priority == 0 {
71 Alias
72 } else {
73 Service
74 }
75 }
76}
77
78#[derive(Debug, Clone, Eq)]
79pub enum ServiceParameter {
80 MANDATORY {
82 key_ids: Vec<u16>,
85 },
86 ALPN {
88 alpn_ids: Vec<String>,
90 },
91 NO_DEFAULT_ALPN,
96 PORT { port: u16 },
98 IPV4_HINT { hints: Vec<Ipv4Addr> },
100 ECH { config_list: Vec<u8> },
104 IPV6_HINT { hints: Vec<Ipv6Addr> },
106 PRIVATE { number: u16, wire_data: Vec<u8> },
108 KEY_65535,
110}
111
112impl PartialEq for ServiceParameter {
113 fn eq(&self, other: &Self) -> bool {
114 self.get_registered_number()
115 .eq(&other.get_registered_number())
116 }
117}
118
119impl Hash for ServiceParameter {
120 fn hash<H: Hasher>(&self, state: &mut H) {
121 state.write_u16(self.get_registered_number())
122 }
123}
124
125impl PartialOrd for ServiceParameter {
126 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
127 Some(self.cmp(other))
128 }
129}
130
131impl Ord for ServiceParameter {
132 fn cmp(&self, other: &Self) -> Ordering {
133 self.get_registered_number()
134 .cmp(&other.get_registered_number())
135 }
136}
137
138impl ServiceParameter {
139 pub fn get_registered_number(&self) -> u16 {
140 match self {
141 ServiceParameter::MANDATORY { .. } => 0,
142 ServiceParameter::ALPN { .. } => 1,
143 ServiceParameter::NO_DEFAULT_ALPN => 2,
144 ServiceParameter::PORT { .. } => 3,
145 ServiceParameter::IPV4_HINT { .. } => 4,
146 ServiceParameter::ECH { .. } => 5,
147 ServiceParameter::IPV6_HINT { .. } => 6,
148 ServiceParameter::PRIVATE {
149 number,
150 wire_data: _,
151 } => *number,
152 ServiceParameter::KEY_65535 => 65535,
153 }
154 }
155
156 fn id_to_presentation_name(id: u16) -> String {
157 match id {
158 0 => "mandatory".to_string(),
159 1 => "alpn".to_string(),
160 2 => "no-default-alpn".to_string(),
161 3 => "port".to_string(),
162 4 => "ipv4hint".to_string(),
163 5 => "ech".to_string(),
164 6 => "ipv6hint".to_string(),
165 65535 => "reserved".to_string(),
166 number => format!("key{}", number),
167 }
168 }
169}
170
171fn escape_alpn(alpn: &str) -> String {
173 let mut result = String::new();
174 for char in alpn.chars() {
175 if char == '\\' {
176 result.push_str("\\\\\\");
177 } else if char == ',' {
178 result.push('\\');
179 }
180 result.push(char);
181 }
182 result
183}
184
185impl Display for ServiceParameter {
186 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
187 match self {
188 ServiceParameter::MANDATORY { key_ids } => {
189 let mut key_ids = key_ids.clone();
190 key_ids.sort_unstable();
191 let mandatory_keys = key_ids
192 .iter()
193 .map(|id| ServiceParameter::id_to_presentation_name(*id))
194 .collect::<Vec<String>>()
195 .join(",");
196
197 write!(f, "mandatory={}", mandatory_keys)
198 }
199 ServiceParameter::ALPN { alpn_ids } => {
200 let mut escape = false;
201 let mut escaped_ids = vec![];
202 for id in alpn_ids {
203 let escaped = escape_alpn(id);
204 if escaped != *id {
205 escape |= true;
206 }
207 escaped_ids.push(escaped);
208 }
209 let value = escaped_ids.join(",");
210 if escape {
211 write!(f, "alpn=\"{}\"", value)
212 } else {
213 write!(f, "alpn={}", value)
214 }
215 }
216 ServiceParameter::NO_DEFAULT_ALPN => write!(f, "no-default-alpn"),
217 ServiceParameter::PORT { port } => write!(f, "port={}", port),
218 ServiceParameter::IPV4_HINT { hints } => {
219 write!(
220 f,
221 "ipv4hint={}",
222 hints
223 .iter()
224 .map(|hint| hint.to_string())
225 .collect::<Vec<String>>()
226 .join(",")
227 )
228 }
229 ServiceParameter::ECH { config_list } => {
230 write!(f, "ech={}", Base64Standard.encode(config_list))
231 }
232 ServiceParameter::IPV6_HINT { hints } => {
233 write!(
234 f,
235 "ipv6hint=\"{}\"",
236 hints
237 .iter()
238 .map(|hint| hint.to_string())
239 .collect::<Vec<String>>()
240 .join(",")
241 )
242 }
243 ServiceParameter::PRIVATE { number, wire_data } => {
244 let key = format!("key{}", number);
245 let value = String::from_utf8(wire_data.clone());
246 if let Ok(value) = value {
247 write!(f, "{}={}", key, value)
248 } else {
249 let mut escaped = vec![];
250 for byte in wire_data {
251 if *byte < b'0'
252 || (*byte > b'9' && *byte < b'A')
253 || (*byte > b'Z' && *byte < b'a')
254 || *byte > b'z'
255 {
256 escaped.extend_from_slice(format!("\\{}", *byte).as_bytes());
257 } else {
258 escaped.push(*byte);
259 }
260 }
261 if let Ok(value) = String::from_utf8(escaped) {
262 write!(f, "{}=\"{}\"", key, value)
263 } else {
264 write!(f, "{}=\"{}\"", key, Base64Standard.encode(wire_data))
265 }
266 }
267 }
268 ServiceParameter::KEY_65535 => write!(f, "reserved"),
269 }
270 }
271}