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:
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) =