Repo of no-std crates for my personal embedded projects
1use core::net::IpAddr;
2
3use alloc::{
4 string::{String, ToString},
5 vec::Vec,
6};
7
8use crate::dns::{
9 label::Label,
10 query::{Answer, QClass},
11 records::{A, AAAA, PTR, QType, Record, SRV, TXT},
12};
13
14#[derive(Debug, Default)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub struct Service {
17 service_type: String,
18 instance: String,
19 hostname: String,
20 ip: Option<IpAddr>,
21 port: u16,
22}
23
24impl Service {
25 pub fn new(
26 service_type: impl Into<String>,
27 instance: impl Into<String>,
28 hostname: impl Into<String>,
29 ip: Option<IpAddr>,
30 port: u16,
31 ) -> Self {
32 let service_type = service_type.into();
33 let mut instance = instance.into();
34 let mut hostname = hostname.into();
35
36 instance.push('.');
37 instance.push_str(&service_type);
38 hostname.push_str(".local");
39
40 Self {
41 service_type,
42 instance,
43 hostname,
44 ip,
45 port,
46 }
47 }
48
49 pub fn service_type(&self) -> Label<'_> {
50 Label::from(self.service_type.as_ref())
51 }
52
53 pub fn instance(&self) -> Label<'_> {
54 Label::from(self.instance.as_ref())
55 }
56
57 pub fn hostname(&self) -> Label<'_> {
58 Label::from(self.hostname.as_ref())
59 }
60
61 pub fn ip(&self) -> Option<IpAddr> {
62 self.ip
63 }
64
65 pub fn port(&self) -> u16 {
66 self.port
67 }
68
69 pub(crate) fn ptr_answer(&self, aclass: QClass) -> Option<Answer<'_>> {
70 Some(Answer {
71 name: self.service_type(),
72 atype: QType::PTR,
73 aclass,
74 ttl: 4500,
75 record: Record::PTR(PTR {
76 name: self.instance(),
77 }),
78 })
79 }
80
81 pub(crate) fn srv_answer(&self, aclass: QClass) -> Option<Answer<'_>> {
82 Some(Answer {
83 name: self.instance(),
84 atype: QType::SRV,
85 aclass,
86 ttl: 120,
87 record: Record::SRV(SRV {
88 priority: 0,
89 weight: 0,
90 port: self.port,
91 target: self.hostname(),
92 }),
93 })
94 }
95
96 pub(crate) fn txt_answer(&self, aclass: QClass) -> Option<Answer<'_>> {
97 Some(Answer {
98 name: self.instance(),
99 atype: QType::TXT,
100 aclass,
101 ttl: 120,
102 record: Record::TXT(TXT { text: Vec::new() }),
103 })
104 }
105
106 pub(crate) fn ip_answer(&self, aclass: QClass) -> Option<Answer<'_>> {
107 self.ip().map(|address| match address {
108 IpAddr::V4(address) => Answer {
109 name: self.hostname(),
110 atype: QType::A,
111 aclass,
112 ttl: 120,
113 record: Record::A(A { address }),
114 },
115 IpAddr::V6(address) => Answer {
116 name: self.hostname(),
117 atype: QType::AAAA,
118 aclass,
119 ttl: 120,
120 record: Record::AAAA(AAAA { address }),
121 },
122 })
123 }
124
125 pub(crate) fn as_answers(&self, aclass: QClass) -> impl Iterator<Item = Answer<'_>> {
126 self.ptr_answer(aclass)
127 .into_iter()
128 .chain(self.srv_answer(aclass))
129 .chain(self.txt_answer(aclass))
130 .chain(self.ip_answer(aclass))
131 }
132
133 #[allow(dead_code)]
134 pub(crate) fn from_answers(answers: &[Answer<'_>]) -> Vec<Self> {
135 let mut output = Vec::new();
136
137 // Step 1: Process PTR records
138 for answer in answers {
139 if let Record::PTR(ptr) = &answer.record {
140 let instance = ptr.name.to_string();
141 let service_type = answer.name.to_string();
142 output.push(Self {
143 service_type,
144 instance,
145 ..Default::default()
146 });
147 }
148 }
149
150 // Step 2: Process SRV records, A and AAAA records and merge data
151 for answer in answers {
152 match &answer.record {
153 Record::SRV(srv) => {
154 if let Some(stub) = output
155 .iter_mut()
156 .find(|stub| answer.name == stub.instance.as_ref())
157 {
158 stub.hostname = srv.target.to_string();
159 stub.port = srv.port;
160 }
161 }
162 Record::A(a) => {
163 if let Some(stub) = output
164 .iter_mut()
165 .find(|stub| answer.name == stub.hostname.as_ref())
166 {
167 stub.ip = Some(IpAddr::V4(a.address));
168 }
169 }
170 Record::AAAA(aaaa) => {
171 if let Some(stub) = output
172 .iter_mut()
173 .find(|stub| answer.name == stub.hostname.as_ref())
174 {
175 stub.ip = Some(IpAddr::V6(aaaa.address));
176 }
177 }
178 _ => {}
179 }
180 }
181
182 // Final step: Retain only complete services
183 output.retain(|stub| {
184 !stub.service_type.is_empty()
185 && !stub.instance.is_empty()
186 && !stub.hostname.is_empty()
187 && stub.ip.is_some()
188 && stub.port != 0
189 });
190
191 output
192 }
193}