1use std::collections::HashMap;
2use std::net::{Ipv4Addr, Ipv6Addr};
3
4use crate::protocol::types::*;
5use crate::zones::types::*;
6
7pub const TTL: u32 = 5;
9
10#[derive(Debug, Clone, Eq, PartialEq)]
12#[cfg_attr(any(feature = "test-util", test), derive(arbitrary::Arbitrary))]
13pub struct Hosts {
14 pub v4: HashMap<DomainName, Ipv4Addr>,
15 pub v6: HashMap<DomainName, Ipv6Addr>,
16}
17
18impl Hosts {
19 pub fn new() -> Self {
20 Self {
21 v4: HashMap::new(),
22 v6: HashMap::new(),
23 }
24 }
25
26 pub fn merge(&mut self, other: Hosts) {
29 for (name, address) in other.v4 {
30 self.v4.insert(name, address);
31 }
32 for (name, address) in other.v6 {
33 self.v6.insert(name, address);
34 }
35 }
36
37 pub fn from_zone_lossy(zone: &Zone) -> Self {
40 let mut v4 = HashMap::new();
41 let mut v6 = HashMap::new();
42 for (name, zrs) in zone.all_records() {
43 for zr in zrs {
44 let rr = zr.to_rr(name);
45 match rr.rtype_with_data {
46 RecordTypeWithData::A { address } => {
47 v4.insert(rr.name, address);
48 }
49 RecordTypeWithData::AAAA { address } => {
50 v6.insert(rr.name, address);
51 }
52 _ => (),
53 }
54 }
55 }
56
57 Self { v4, v6 }
58 }
59}
60
61impl Default for Hosts {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl From<Hosts> for Zone {
68 fn from(hosts: Hosts) -> Zone {
69 let mut zone = Self::default();
70 for (name, address) in hosts.v4 {
71 zone.insert(&name, RecordTypeWithData::A { address }, TTL);
72 }
73 for (name, address) in hosts.v6 {
74 zone.insert(&name, RecordTypeWithData::AAAA { address }, TTL);
75 }
76 zone
77 }
78}
79
80impl TryFrom<Zone> for Hosts {
81 type Error = TryFromZoneError;
82
83 fn try_from(zone: Zone) -> Result<Self, Self::Error> {
88 if !zone.all_wildcard_records().is_empty() {
89 return Err(TryFromZoneError::HasWildcardRecords);
90 }
91
92 let mut v4 = HashMap::new();
93 let mut v6 = HashMap::new();
94 for (name, zrs) in zone.all_records() {
95 for zr in zrs {
96 let rr = zr.to_rr(name);
97 match rr.rtype_with_data {
98 RecordTypeWithData::A { address } => {
99 v4.insert(rr.name, address);
100 }
101 RecordTypeWithData::AAAA { address } => {
102 v6.insert(rr.name, address);
103 }
104 _ => return Err(TryFromZoneError::HasRecordTypesOtherThanA),
105 }
106 }
107 }
108
109 Ok(Self { v4, v6 })
110 }
111}
112
113#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
115pub enum TryFromZoneError {
116 HasWildcardRecords,
117 HasRecordTypesOtherThanA,
118}
119
120#[cfg(test)]
121mod tests {
122 use crate::protocol::types::test_util::*;
123
124 use super::test_util::*;
125 use super::*;
126
127 #[test]
128 fn hosts_zone_roundtrip() {
129 for _ in 0..100 {
130 let expected = arbitrary_hosts();
131 if let Ok(actual) = Hosts::try_from(Zone::from(expected.clone())) {
132 assert_eq!(expected, actual);
133 } else {
134 panic!("expected round-trip");
135 }
136 }
137 }
138
139 #[test]
140 fn hosts_merge_zone_merge_equiv_when_disjoint() {
141 for _ in 0..100 {
142 let hosts1 = arbitrary_hosts_with_apex(&domain("hosts1."));
143 let hosts2 = arbitrary_hosts_with_apex(&domain("hosts2."));
144
145 let mut combined_hosts = hosts1.clone();
146 combined_hosts.merge(hosts2.clone());
147
148 let combined_zone_direct = Zone::from(combined_hosts.clone());
149 let mut combined_zone_indirect = Zone::from(hosts1);
150 combined_zone_indirect.merge(hosts2.into()).unwrap();
151
152 assert_eq!(combined_zone_direct, combined_zone_indirect);
153 assert_eq!(Ok(combined_hosts), combined_zone_direct.try_into());
154 }
155 }
156
157 fn arbitrary_hosts_with_apex(apex: &DomainName) -> Hosts {
158 let arbitrary = arbitrary_hosts();
159
160 let mut out = Hosts::new();
161 for (k, v) in arbitrary.v4 {
162 out.v4.insert(k.make_subdomain_of(apex).unwrap(), v);
163 }
164 for (k, v) in arbitrary.v6 {
165 out.v6.insert(k.make_subdomain_of(apex).unwrap(), v);
166 }
167 out
168 }
169}
170
171#[cfg(any(feature = "test-util", test))]
172#[allow(clippy::missing_panics_doc)]
173pub mod test_util {
174 use super::*;
175
176 use arbitrary::{Arbitrary, Unstructured};
177 use rand::Rng;
178
179 pub fn arbitrary_hosts() -> Hosts {
180 let mut rng = rand::rng();
181 for size in [128, 256, 512, 1024, 2048, 4096] {
182 let mut buf = Vec::new();
183 for _ in 0..size {
184 buf.push(rng.random());
185 }
186
187 if let Ok(rr) = Hosts::arbitrary(&mut Unstructured::new(&buf)) {
188 return rr;
189 }
190 }
191
192 panic!("could not generate arbitrary value!");
193 }
194}