dns_types/zones/
serialise.rs1use bytes::Bytes;
2use std::collections::HashSet;
3use std::fmt::Write as _;
4
5use crate::protocol::types::*;
6use crate::zones::types::*;
7
8impl Zone {
9 pub fn serialise(&self) -> String {
10 let mut out = String::new();
11
12 if let Some(soa) = self.get_soa() {
13 let show_origin = !self.get_apex().is_root();
14 let serialised_apex = serialise_octets(
15 &self
16 .get_apex()
17 .to_dotted_string()
18 .bytes()
19 .collect::<Bytes>(),
20 false,
21 );
22
23 if show_origin {
24 _ = writeln!(&mut out, "$ORIGIN {serialised_apex}");
25 out.push('\n');
26 }
27
28 _ = writeln!(
29 &mut out,
30 "{} IN SOA {}",
31 if show_origin { "@" } else { &serialised_apex },
32 self.serialise_rdata(&soa.to_rdata()),
33 );
34 out.push('\n');
35 }
36
37 let all_records = self.all_records();
38 let all_wildcard_records = self.all_wildcard_records();
39
40 let sorted_domains = {
41 let mut set = HashSet::new();
42 for name in all_records.keys() {
43 set.insert(*name);
44 }
45 for name in all_wildcard_records.keys() {
46 set.insert(*name);
47 }
48 let mut vec = set.into_iter().collect::<Vec<&DomainName>>();
49 vec.sort();
50 vec
51 };
52
53 for domain in sorted_domains {
54 if let Some(zrs) = all_records.get(domain) {
55 let has_wildcards = all_wildcard_records.contains_key(domain);
56 for zr in zrs {
57 if zr.rtype_with_data.rtype() == RecordType::SOA {
58 continue;
61 }
62
63 _ = writeln!(
64 &mut out,
65 "{}{} {} IN {} {}",
66 self.serialise_domain(domain),
67 if has_wildcards { " " } else { "" },
68 zr.ttl,
69 zr.rtype_with_data.rtype(),
70 self.serialise_rdata(&zr.rtype_with_data)
71 );
72 }
73 }
74 if let Some(zrs) = all_wildcard_records.get(domain) {
75 for zr in zrs {
76 _ = writeln!(
77 &mut out,
78 "*.{} {} IN {} {}",
79 self.serialise_domain(domain),
80 zr.ttl,
81 zr.rtype_with_data.rtype(),
82 self.serialise_rdata(&zr.rtype_with_data)
83 );
84 }
85 }
86 out.push('\n');
87 }
88
89 out
90 }
91
92 fn serialise_domain(&self, name: &DomainName) -> String {
97 let domain_str = {
98 let apex = self.get_apex();
99 if apex.is_root() || !self.is_authoritative() || !name.is_subdomain_of(apex) {
100 name.to_dotted_string()
101 } else if name == apex {
102 "@".to_string()
103 } else {
104 let labels_to_keep = name.labels.len() - apex.labels.len();
105 DomainName {
106 labels: Vec::from(&name.labels[..labels_to_keep]),
107 len: name.len - apex.len,
108 }
109 .to_dotted_string()
110 }
111 };
112
113 serialise_octets(&domain_str.bytes().collect::<Bytes>(), false)
114 }
115
116 pub fn serialise_rdata(&self, rtype_with_data: &RecordTypeWithData) -> String {
119 match rtype_with_data {
120 RecordTypeWithData::A { address } => format!("{address}"),
121 RecordTypeWithData::NS { nsdname } => self.serialise_domain(nsdname),
122 RecordTypeWithData::MD { madname } => self.serialise_domain(madname),
123 RecordTypeWithData::MF { madname } => self.serialise_domain(madname),
124 RecordTypeWithData::CNAME { cname } => self.serialise_domain(cname),
125 RecordTypeWithData::SOA {
126 mname,
127 rname,
128 serial,
129 refresh,
130 retry,
131 expire,
132 minimum,
133 } => format!(
134 "{} {} {serial} {refresh} {retry} {expire} {minimum}",
135 self.serialise_domain(mname),
136 self.serialise_domain(rname),
137 ),
138 RecordTypeWithData::MB { madname } => self.serialise_domain(madname),
139 RecordTypeWithData::MG { mdmname } => self.serialise_domain(mdmname),
140 RecordTypeWithData::MR { newname } => self.serialise_domain(newname),
141 RecordTypeWithData::NULL { octets } => serialise_octets(octets, true),
142 RecordTypeWithData::WKS { octets } => serialise_octets(octets, true),
143 RecordTypeWithData::PTR { ptrdname } => self.serialise_domain(ptrdname),
144 RecordTypeWithData::HINFO { octets } => serialise_octets(octets, true),
145 RecordTypeWithData::MINFO { rmailbx, emailbx } => format!(
146 "{} {}",
147 self.serialise_domain(rmailbx),
148 self.serialise_domain(emailbx)
149 ),
150 RecordTypeWithData::MX {
151 preference,
152 exchange,
153 } => format!("{preference} {}", self.serialise_domain(exchange)),
154 RecordTypeWithData::TXT { octets } => serialise_octets(octets, true),
155 RecordTypeWithData::AAAA { address } => format!("{address}"),
156 RecordTypeWithData::SRV {
157 priority,
158 weight,
159 port,
160 target,
161 } => format!(
162 "{priority} {weight} {port} {}",
163 self.serialise_domain(target)
164 ),
165 RecordTypeWithData::Unknown { octets, .. } => serialise_octets(octets, true),
166 }
167 }
168}
169
170fn serialise_octets(octets: &[u8], quoted: bool) -> String {
173 let mut out = String::with_capacity(2 + octets.len());
174
175 if quoted {
176 out.push('"');
177 }
178
179 for octet in octets {
180 if *octet == b'"' || *octet == b'\\' || *octet == b';' || *octet == b'(' || *octet == b')' {
181 out.push('\\');
182 out.push(*octet as char);
183 } else if *octet < 32 || *octet > 126 || (*octet == 32 && !quoted) {
184 out.push('\\');
185 let digit3 = *octet % 10;
186 let digit2 = (*octet / 10) % 10;
187 let digit1 = (*octet / 100) % 10;
188 out.push((digit1 + 48) as char);
189 out.push((digit2 + 48) as char);
190 out.push((digit3 + 48) as char);
191 } else {
192 out.push(*octet as char);
193 }
194 }
195
196 if quoted {
197 out.push('"');
198 }
199
200 out
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn serialise_octets_special() {
209 assert_eq!("\\012", serialise_octets(&[12], false));
210 assert_eq!("\\234", serialise_octets(&[234], false));
211
212 assert_eq!("\\\\", serialise_octets(b"\\", false));
213 assert_eq!("\\\"", serialise_octets(b"\"", false));
214 }
215
216 #[test]
217 fn serialise_octets_space() {
218 assert_eq!("\\032", serialise_octets(b" ", false));
219 assert_eq!("\" \"", serialise_octets(b" ", true));
220 }
221}