eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 31306f258dac980408f2ff80dbcfec153843b76e
parent 59c2bb1ba18db904c883a82cc6f26a2d54afb456
Author: Daniel Barlow <dan@telent.net>
Date:   Fri,  2 Jan 2026 20:27:48 +0000

extract mdns into separate file

Diffstat:
Mecu-esp32/src/main.rs | 2+-
Aecu-esp32/src/mdns.rs | 460+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mecu-esp32/src/wifi.rs | 451+------------------------------------------------------------------------------
3 files changed, 463 insertions(+), 450 deletions(-)

diff --git a/ecu-esp32/src/main.rs b/ecu-esp32/src/main.rs @@ -9,9 +9,9 @@ use esp_backtrace as _; use esp_println as _; -pub mod wibble; pub mod ecu; pub mod wifi; +pub mod mdns; use esp_hal::{ timer::timg::TimerGroup, diff --git a/ecu-esp32/src/mdns.rs b/ecu-esp32/src/mdns.rs @@ -0,0 +1,460 @@ +use alloc::vec::Vec; +use alloc::string::String; +use alloc::collections::BTreeSet; +use crate::alloc::string::ToString; + +use embassy_net::{ + udp::{UdpSocket, UdpMetadata}, + IpAddress, + IpEndpoint, + Stack +}; + +use embassy_time::{Duration, Instant}; +use esp_alloc as _; +use esp_backtrace as _; + +use log::{info,error}; + +pub const DISCOVERY_NAME : &str = "_services._dns-sd._udp.local"; + +use dns_protocol::{Message, Question, + MessageType, + ResourceRecord, ResourceType, Flags}; + +const MDNS_IP: IpAddress = IpAddress::v4(224, 0, 0, 251); +const MDNS_PORT: u16 = 5353; +const MDNS_ENDPOINT: IpEndpoint = IpEndpoint::new(MDNS_IP, MDNS_PORT); + +const CACHE_FLUSH : u16 = 1<<15; + +pub enum MdnsResponderState { + Probing(Instant, usize), + ProbeFailed, + Announcing(Instant, usize), + Running +} + +pub struct MdnsResponder<'a> { + sock : UdpSocket<'a>, + pub database : DnsDatabase, + pub state : MdnsResponderState +} + +impl<'a> MdnsResponder<'a> { + pub fn new(stack : Stack<'a>, + mut sock: UdpSocket<'a>, + database: DnsDatabase) -> MdnsResponder<'a> { + + sock.set_hop_limit(Some(255)); + log::info!("sock.bind(5353) res: {:?}", sock.bind(MDNS_PORT)); + log::info!( + "multicast_res: {:?}", + stack.join_multicast_group(MDNS_IP) + ); + + Self { + sock: sock, + database, + state: MdnsResponderState::Probing(Instant::now(), 0) + } + } + + async fn advance_state(&mut self) { + match self.state { + MdnsResponderState::Probing(next_when, number_sent) => { + let ts = Instant::now(); + if next_when < ts { + if number_sent >= 3 { + self.state = MdnsResponderState::Announcing(ts, 0); + } else { + self.send_probe().await; + self.state = MdnsResponderState::Probing(ts + Duration::from_millis(250), number_sent + 1); + } + } + }, + MdnsResponderState::Announcing(next_when, number_sent) => { + let ts = Instant::now(); + if next_when < ts { + if number_sent > 6 { + self.state = MdnsResponderState::Running; + } else { + self.advertise().await; + self.state = MdnsResponderState::Announcing(ts + Duration::from_millis(1000), number_sent + 1); + } + } + }, + MdnsResponderState::Running => (), + MdnsResponderState::ProbeFailed => (), + } + } + + async fn send_message(&self, message: Message<'_, '_>, endpoint: IpEndpoint) { + let mut data_buf = [0; 4096]; + // Serialize the message into the buffer + assert!(message.space_needed() <= data_buf.len()); + + match message.write(&mut data_buf) { + Result::Ok(len) => { + if let Result::Err(x) = self.sock.send_to(&data_buf[..len], endpoint).await { + info!("sock.send failed: {:?}", x); + } + }, + Result::Err(err) => { + error!("message.write {:?}", err); + } + }; + } + + async fn send_probe(&self) { + info!("sending probe at {}", Instant::now().as_millis()); + + let db = &self.database; + + let mut questions : Vec<Question> = db.advertised().iter().filter_map( |record| { + // FIXME this sends duplicates because there's both an SRV + // and a TXT for the same name + if record.is_unique() { + Some( + Question::new( + &record.name[..], + ResourceType::Wildcard, + 1 | (1<<15) // QU + )) + } else { + None + } + }).collect(); + + let message = Message::new( + 0, Flags::standard_query(), &mut questions, &mut [], &mut [], &mut [] + ); + + self.send_message(message, MDNS_ENDPOINT).await; + } + + async fn advertise(&self) { + info!("advertise at {}", Instant::now().as_millis()); + self.send_response(0, MDNS_ENDPOINT, None, self.database.advertised()).await; + } + + pub async fn may_recv(&mut self) -> bool { + self.advance_state().await; + match self.state { + MdnsResponderState::ProbeFailed => false, + _ => self.sock.may_recv() + } + } + + async fn send_response(&self, + id: u16, + endpoint: IpEndpoint, + question: Option<&Question<'_>>, + answers: Vec<&DnsResource>) { + + let legacy = question != None; + + let db = &self.database; + let mut flags = Flags::new(); + + flags.set_qr(MessageType::Reply); + flags.set_authoritative(true); + + let mut additional : BTreeSet<ResourceRecord> = BTreeSet::new(); + let mut answers : Vec<ResourceRecord> = { + let mut rrs = Vec::new(); + for a in answers { + rrs.push(a.to_resource_record()); + for r in db.all_additional(a).into_iter() { + additional.insert(r.to_resource_record()); + }; + } + rrs + }; + + // don't send anything in "additional" that's already in the answers + for rr in &answers { + additional.remove(&rr); + } + + let mut additional : Vec<ResourceRecord> = additional.into_iter().collect(); + + let questions = match question { + Some(q) =>&mut [*q][..], + None => &mut [][..] + }; + + if legacy { + // force class = IN (no cache-flush) and ttl to 10 seconds + answers = answers.iter().map(|rr| { + ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data()) + }).collect(); + additional = additional.iter().map(|rr| { + ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data()) + }).collect(); + } + + let message = Message::new( + id, + flags, + questions, + &mut answers, + &mut [], + &mut additional); + + self.send_message(message, endpoint).await; + } + + async fn process_received_message(&mut self, + message: &Message<'_,'_>, + meta: &UdpMetadata) { + + match self.state { + MdnsResponderState::Probing(_,_) => { + for rr in message.answers() { + info!("received answer {:?}", rr.name()); + if self.database.find(rr.name().to_string(), ResourceType::Wildcard) + .iter().any(|record| record.is_unique()) { + info!("probe failed, received answer for {:?}", rr.name()); + self.state = MdnsResponderState::ProbeFailed; + } + } + }, + MdnsResponderState::ProbeFailed => (), + _ => { + let q_id = message.id(); + + for q in message.questions() { + let relevant = self.database.find(q.name().to_string(), q.ty()); + if relevant.len() > 0 { + log::info!("question {:?} {:?} {:?} flags {:?} from {:?}", + q.name(), q.ty(), q.class(), + message.flags().raw(), + meta.endpoint); + + let unicast = (q.class() & (1<<15)) != 0; + let legacy = meta.endpoint.port != 5353; + if legacy { + self.send_response(q_id, meta.endpoint, Some(q), relevant).await + } else if unicast { + self.send_response(0, meta.endpoint, None, relevant).await + } else { + self.send_response(0, MDNS_ENDPOINT, None, relevant).await + } + } + } + } + } + } + + pub async fn process_packet(&mut self) { + let mut questions = [Question::default(); 64]; + let mut answers = [ResourceRecord::default(); 64]; + let mut authorities = [ResourceRecord::default(); 64]; + let mut addl = [ResourceRecord::default(); 64]; + + let mut data_buf = [0; 4096]; + + let res = self.sock.recv_from(&mut data_buf).await; + if let Ok((n, meta)) = res { + let message = Message::read( + &data_buf[..n], + &mut questions, + &mut answers, + &mut authorities, + &mut addl, + ); + + match message { + Result::Ok(message) => { + self.process_received_message(&message, &meta).await; + }, + Result::Err(err) => { + log::error!("parsing dns message from {:?} : {:?}", + meta.endpoint, + err); + } + } + } + } +} + + +// dns_protocol's ResourceRecord doesn't make a good representation for +// our needs because it doesn't own its "data" attribute - it's just a +// reference. So we need structures that can own the data + +#[derive(Clone,Debug)] +pub enum RData { + Srv { target: String, port: u16, priority: u16, weight: u16 }, + Ptr(String), + A([u8; 4]), + Txt(Vec<String>) +} + +impl RData { + fn copy_string<'a>(s: &str, out: &'a mut [u8], offset: usize) -> usize { + let end = s.len() + offset + 1; + out[offset] = (s.len() & 0xff) as u8; + let _ = &out[(offset+1) .. end].clone_from_slice(s.as_bytes()); + end + } + + fn encode_as_name<'a>(name: &str, out: &'a mut [u8], offset: usize) -> &'a [u8] { + let mut offset = offset; + + for l in name.split('.') { + offset = Self::copy_string(l, out, offset); + } + out[offset] = 0; + &out[..offset+1] + } + + fn encode_as_strings<'a>(strings: &[String], out: &'a mut [u8], offset: usize) -> &'a [u8] { + let mut offset = offset; + + for l in strings.into_iter() { + offset = Self::copy_string(l, out, offset); + } + out[offset] = 0; + &out[..offset+1] + } + + fn ty(&self) -> ResourceType { + match self { + RData::Srv { .. } => ResourceType::Srv, + RData::Ptr(_name) => ResourceType::Ptr, + RData::A(_octets) => ResourceType::A, + RData::Txt(_strings) => ResourceType::Txt + } + } + + fn to_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] { + match self { + RData::Srv { target, port, priority, weight } => { + out[0] = (priority >> 8) as u8; + out[1] = (priority & 0xff) as u8; + out[2] = (weight >> 8) as u8; + out[3] = (weight & 0xff) as u8; + out[4] = (port >> 8) as u8; + out[5] = (port & 0xff) as u8; + Self::encode_as_name(target, out, 6) + }, + RData::Ptr(name) => { + Self::encode_as_name(name, out, 0) + } + RData::A(octets) => { + out[..4].clone_from_slice(octets); + &out[..4] + }, + RData::Txt(strings) => { + Self::encode_as_strings(strings, out, 0) + } + } + } +} + +#[derive(Debug)] +pub struct DnsResource { + name : String, + data : RData, + data_raw : Vec<u8> +} + +impl DnsResource { + pub fn new( + name: impl Into<String>, + data: RData + ) -> Self { + let mut buf : [u8; 80] = [0; 80]; + let rdata_raw = Vec::from(data.to_bytes(&mut buf)); + Self { + name: name.into(), + data, + data_raw: rdata_raw + } + } + + // Should we send cache-flush with this record? If it's "unique", + // which means "all the records with that name, rrtype, and + // rrclass are conceptually under the control or ownership of a + // single responder". This works out (perhaps by coincidence) to + // be everything except the PTR records + fn is_unique(&self) -> bool { + self.data.ty() != ResourceType::Ptr + } + fn class(&self) -> u16 { + if self.is_unique() { + 1 | CACHE_FLUSH + } else { + 1 + } + } + + fn to_resource_record(&self) -> ResourceRecord<'_> { + let ttl = match self.data.ty() { + ResourceType::A => 120, + ResourceType::Srv => 120, + _ => 4500 + }; + + ResourceRecord::new( + &self.name[..], + self.data.ty(), + self.class(), + ttl, + &self.data_raw[..] + ) + } +} + +pub struct DnsDatabase { + pub records: Vec<DnsResource> +} + +impl DnsDatabase { + fn find<'a>(&self, name: impl Into<String>, ty: ResourceType) -> Vec<&DnsResource> { + let name = name.into(); + let results : Vec<&DnsResource> = + self.records.iter().filter( + |r| + ((ty == ResourceType::Wildcard) || (r.data.ty() == ty)) && + name == r.name + ).collect(); + results + } + + // the records we announce unsolicited at startup + fn advertised(&self) -> Vec<&DnsResource> { + self.records.iter().filter( |r| r.name != DISCOVERY_NAME ).collect() + } + + fn additional(&self, r: &DnsResource) -> Vec<&DnsResource> { + match &r.data { + RData::Srv { target, port:_, priority:_, weight:_ } => { + self.find(target, ResourceType::Wildcard) + }, + RData::Ptr(name) => { + if r.name.eq(DISCOVERY_NAME) { + Vec::from([]) + } else { + self.find(name, ResourceType::Wildcard) + } + }, + _ => Vec::from([]) + } + } + + fn all_additional_<'a>(&'a self, r: &DnsResource, collect: &mut Vec<&'a DnsResource>) { + for r in self.additional(r) { + collect.push(r); + self.all_additional_(r, collect); + } + } + + fn all_additional(&self, r: &DnsResource) -> Vec<&DnsResource> { + let mut collect : Vec<&DnsResource> = Vec::new(); + self.all_additional_(r, &mut collect); + collect + } +} diff --git a/ecu-esp32/src/wifi.rs b/ecu-esp32/src/wifi.rs @@ -6,17 +6,12 @@ use alloc::vec::Vec; use alloc::string::String; use alloc::format; -use alloc::collections::BTreeSet; -use crate::alloc::string::ToString; use embassy_executor::Spawner; use embassy_net::{ Runner, StackResources, - udp::{PacketMetadata, UdpSocket, UdpMetadata}, - IpAddress, - IpEndpoint, - Stack + udp::{PacketMetadata, UdpSocket}, }; use embassy_time::{Duration, Timer, Instant}; @@ -39,13 +34,7 @@ use esp_radio::{ }, }; -use log::{info,error}; - -const DISCOVERY_NAME : &str = "_services._dns-sd._udp.local"; - -use dns_protocol::{Message, Question, - MessageType, - ResourceRecord, ResourceType, Flags}; +use crate::mdns::{MdnsResponder, DnsDatabase, MdnsResponderState, DnsResource, RData, DISCOVERY_NAME}; // When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html macro_rules! mk_static { @@ -60,442 +49,6 @@ macro_rules! mk_static { const SSID: &str = env!("SSID"); const PASSWORD: &str = env!("PASSWORD"); -const MDNS_IP: IpAddress = IpAddress::v4(224, 0, 0, 251); -const MDNS_PORT: u16 = 5353; -const MDNS_ENDPOINT: IpEndpoint = IpEndpoint::new(MDNS_IP, MDNS_PORT); - -const CACHE_FLUSH : u16 = 1<<15; - -enum MdnsResponderState { - Probing(Instant, usize), - ProbeFailed, - Announcing(Instant, usize), - Running -} - -struct MdnsResponder<'a> { - sock : UdpSocket<'a>, - database : DnsDatabase, - state : MdnsResponderState -} - -impl<'a> MdnsResponder<'a> { - fn new(stack : Stack<'a>, - mut sock: UdpSocket<'a>, - database: DnsDatabase) -> MdnsResponder<'a> { - - sock.set_hop_limit(Some(255)); - log::info!("sock.bind(5353) res: {:?}", sock.bind(MDNS_PORT)); - log::info!( - "multicast_res: {:?}", - stack.join_multicast_group(MDNS_IP) - ); - - Self { - sock: sock, - database, - state: MdnsResponderState::Probing(Instant::now(), 0) - } - } - - async fn advance_state(&mut self) { - match self.state { - MdnsResponderState::Probing(next_when, number_sent) => { - let ts = Instant::now(); - if next_when < ts { - if number_sent >= 3 { - self.state = MdnsResponderState::Announcing(ts, 0); - } else { - self.send_probe().await; - self.state = MdnsResponderState::Probing(ts + Duration::from_millis(250), number_sent + 1); - } - } - }, - MdnsResponderState::Announcing(next_when, number_sent) => { - let ts = Instant::now(); - if next_when < ts { - if number_sent > 6 { - self.state = MdnsResponderState::Running; - } else { - self.advertise().await; - self.state = MdnsResponderState::Announcing(ts + Duration::from_millis(1000), number_sent + 1); - } - } - }, - MdnsResponderState::Running => (), - MdnsResponderState::ProbeFailed => (), - } - } - - async fn send_message(&self, message: Message<'_, '_>, endpoint: IpEndpoint) { - let mut data_buf = [0; 4096]; - // Serialize the message into the buffer - assert!(message.space_needed() <= data_buf.len()); - - match message.write(&mut data_buf) { - Result::Ok(len) => { - if let Result::Err(x) = self.sock.send_to(&data_buf[..len], endpoint).await { - info!("sock.send failed: {:?}", x); - } - }, - Result::Err(err) => { - error!("message.write {:?}", err); - } - }; - } - - async fn send_probe(&self) { - info!("sending probe at {}", Instant::now().as_millis()); - - let db = &self.database; - - let mut questions : Vec<Question> = db.advertised().iter().filter_map( |record| { - // FIXME this sends duplicates because there's both an SRV - // and a TXT for the same name - if record.is_unique() { - Some( - Question::new( - &record.name[..], - ResourceType::Wildcard, - 1 | (1<<15) // QU - )) - } else { - None - } - }).collect(); - - let message = Message::new( - 0, Flags::standard_query(), &mut questions, &mut [], &mut [], &mut [] - ); - - self.send_message(message, MDNS_ENDPOINT).await; - } - - async fn advertise(&self) { - info!("advertise at {}", Instant::now().as_millis()); - self.send_response(0, MDNS_ENDPOINT, None, self.database.advertised()).await; - } - - async fn may_recv(&mut self) -> bool { - self.advance_state().await; - match self.state { - MdnsResponderState::ProbeFailed => false, - _ => self.sock.may_recv() - } - } - - async fn send_response(&self, - id: u16, - endpoint: IpEndpoint, - question: Option<&Question<'_>>, - answers: Vec<&DnsResource>) { - - let legacy = question != None; - - let db = &self.database; - let mut flags = Flags::new(); - - flags.set_qr(MessageType::Reply); - flags.set_authoritative(true); - - let mut additional : BTreeSet<ResourceRecord> = BTreeSet::new(); - let mut answers : Vec<ResourceRecord> = { - let mut rrs = Vec::new(); - for a in answers { - rrs.push(a.to_resource_record()); - for r in db.all_additional(a).into_iter() { - additional.insert(r.to_resource_record()); - }; - } - rrs - }; - - // don't send anything in "additional" that's already in the answers - for rr in &answers { - additional.remove(&rr); - } - - let mut additional : Vec<ResourceRecord> = additional.into_iter().collect(); - - let questions = match question { - Some(q) =>&mut [*q][..], - None => &mut [][..] - }; - - if legacy { - // force class = IN (no cache-flush) and ttl to 10 seconds - answers = answers.iter().map(|rr| { - ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data()) - }).collect(); - additional = additional.iter().map(|rr| { - ResourceRecord::new(rr.name(), rr.ty(), 1, 10, rr.data()) - }).collect(); - } - - let message = Message::new( - id, - flags, - questions, - &mut answers, - &mut [], - &mut additional); - - self.send_message(message, endpoint).await; - } - - async fn process_received_message(&mut self, - message: &Message<'_,'_>, - meta: &UdpMetadata) { - - match self.state { - MdnsResponderState::Probing(_,_) => { - for rr in message.answers() { - info!("received answer {:?}", rr.name()); - if self.database.find(rr.name().to_string(), ResourceType::Wildcard) - .iter().any(|record| record.is_unique()) { - info!("probe failed, received answer for {:?}", rr.name()); - self.state = MdnsResponderState::ProbeFailed; - } - } - }, - MdnsResponderState::ProbeFailed => (), - _ => { - let q_id = message.id(); - - for q in message.questions() { - let relevant = self.database.find(q.name().to_string(), q.ty()); - if relevant.len() > 0 { - log::info!("question {:?} {:?} {:?} flags {:?} from {:?}", - q.name(), q.ty(), q.class(), - message.flags().raw(), - meta.endpoint); - - let unicast = (q.class() & (1<<15)) != 0; - let legacy = meta.endpoint.port != 5353; - if legacy { - self.send_response(q_id, meta.endpoint, Some(q), relevant).await - } else if unicast { - self.send_response(0, meta.endpoint, None, relevant).await - } else { - self.send_response(0, MDNS_ENDPOINT, None, relevant).await - } - } - } - } - } - } - - async fn process_packet(&mut self) { - let mut questions = [Question::default(); 64]; - let mut answers = [ResourceRecord::default(); 64]; - let mut authorities = [ResourceRecord::default(); 64]; - let mut addl = [ResourceRecord::default(); 64]; - - let mut data_buf = [0; 4096]; - - let res = self.sock.recv_from(&mut data_buf).await; - if let Ok((n, meta)) = res { - let message = Message::read( - &data_buf[..n], - &mut questions, - &mut answers, - &mut authorities, - &mut addl, - ); - - match message { - Result::Ok(message) => { - self.process_received_message(&message, &meta).await; - }, - Result::Err(err) => { - log::error!("parsing dns message from {:?} : {:?}", - meta.endpoint, - err); - } - } - } - } -} - - -// dns_protocol's ResourceRecord doesn't make a good representation for -// our needs because it doesn't own its "data" attribute - it's just a -// reference. So we need structures that can own the data - -#[derive(Clone,Debug)] -enum RData { - Srv { target: String, port: u16, priority: u16, weight: u16 }, - Ptr(String), - A([u8; 4]), - Txt(Vec<String>) -} - -impl RData { - fn copy_string<'a>(s: &str, out: &'a mut [u8], offset: usize) -> usize { - let end = s.len() + offset + 1; - out[offset] = (s.len() & 0xff) as u8; - let _ = &out[(offset+1) .. end].clone_from_slice(s.as_bytes()); - end - } - - fn encode_as_name<'a>(name: &str, out: &'a mut [u8], offset: usize) -> &'a [u8] { - let mut offset = offset; - - for l in name.split('.') { - offset = Self::copy_string(l, out, offset); - } - out[offset] = 0; - &out[..offset+1] - } - - fn encode_as_strings<'a>(strings: &[String], out: &'a mut [u8], offset: usize) -> &'a [u8] { - let mut offset = offset; - - for l in strings.into_iter() { - offset = Self::copy_string(l, out, offset); - } - out[offset] = 0; - &out[..offset+1] - } - - fn ty(&self) -> ResourceType { - match self { - RData::Srv { .. } => ResourceType::Srv, - RData::Ptr(_name) => ResourceType::Ptr, - RData::A(_octets) => ResourceType::A, - RData::Txt(_strings) => ResourceType::Txt - } - } - - fn to_bytes<'a>(&self, out: &'a mut [u8]) -> &'a [u8] { - match self { - RData::Srv { target, port, priority, weight } => { - out[0] = (priority >> 8) as u8; - out[1] = (priority & 0xff) as u8; - out[2] = (weight >> 8) as u8; - out[3] = (weight & 0xff) as u8; - out[4] = (port >> 8) as u8; - out[5] = (port & 0xff) as u8; - Self::encode_as_name(target, out, 6) - }, - RData::Ptr(name) => { - Self::encode_as_name(name, out, 0) - } - RData::A(octets) => { - out[..4].clone_from_slice(octets); - &out[..4] - }, - RData::Txt(strings) => { - Self::encode_as_strings(strings, out, 0) - } - } - } -} - -#[derive(Debug)] -struct DnsResource { - name : String, - data : RData, - data_raw : Vec<u8> -} - -impl DnsResource { - fn new( - name: impl Into<String>, - data: RData - ) -> Self { - let mut buf : [u8; 80] = [0; 80]; - let rdata_raw = Vec::from(data.to_bytes(&mut buf)); - Self { - name: name.into(), - data, - data_raw: rdata_raw - } - } - - // Should we send cache-flush with this record? If it's "unique", - // which means "all the records with that name, rrtype, and - // rrclass are conceptually under the control or ownership of a - // single responder". This works out (perhaps by coincidence) to - // be everything except the PTR records - fn is_unique(&self) -> bool { - self.data.ty() != ResourceType::Ptr - } - fn class(&self) -> u16 { - if self.is_unique() { - 1 | CACHE_FLUSH - } else { - 1 - } - } - - fn to_resource_record(&self) -> ResourceRecord<'_> { - let ttl = match self.data.ty() { - ResourceType::A => 120, - ResourceType::Srv => 120, - _ => 4500 - }; - - ResourceRecord::new( - &self.name[..], - self.data.ty(), - self.class(), - ttl, - &self.data_raw[..] - ) - } -} - -struct DnsDatabase { - records: Vec<DnsResource> -} - -impl DnsDatabase { - fn find<'a>(&self, name: impl Into<String>, ty: ResourceType) -> Vec<&DnsResource> { - let name = name.into(); - let results : Vec<&DnsResource> = - self.records.iter().filter( - |r| - ((ty == ResourceType::Wildcard) || (r.data.ty() == ty)) && - name == r.name - ).collect(); - results - } - - // the records we announce unsolicited at startup - fn advertised(&self) -> Vec<&DnsResource> { - self.records.iter().filter( |r| r.name != DISCOVERY_NAME ).collect() - } - - fn additional(&self, r: &DnsResource) -> Vec<&DnsResource> { - match &r.data { - RData::Srv { target, port:_, priority:_, weight:_ } => { - self.find(target, ResourceType::Wildcard) - }, - RData::Ptr(name) => { - if r.name.eq(DISCOVERY_NAME) { - Vec::from([]) - } else { - self.find(name, ResourceType::Wildcard) - } - }, - _ => Vec::from([]) - } - } - - fn all_additional_<'a>(&'a self, r: &DnsResource, collect: &mut Vec<&'a DnsResource>) { - for r in self.additional(r) { - collect.push(r); - self.all_additional_(r, collect); - } - } - - fn all_additional(&self, r: &DnsResource) -> Vec<&DnsResource> { - let mut collect : Vec<&DnsResource> = Vec::new(); - self.all_additional_(r, &mut collect); - collect - } -} fn dns_records(my_ip_address: [u8;4], serial: Option<usize>) -> Vec<DnsResource> { let (hostname, ptr_name) =