use crate::{
dns::{AnswerSection, DnsMessage, QuerySection, Target},
DEFAULT_BIND_ADRESS, DEFAULT_TTL,
};
use hostname;
use std::{
cmp::Ordering,
collections::HashMap,
ops::{Deref, DerefMut},
};
pub type HashMapRecord = HashMap<String, Vec<Record>>;
#[derive(Debug, Clone, Default)]
pub struct MapRecord(pub(crate) HashMapRecord);
impl Deref for MapRecord {
type Target = HashMapRecord;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for MapRecord {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl MapRecord {
pub fn new() -> Self {
Self(HashMapRecord::default())
}
pub fn with_record(networkid: &str, record: &[Record]) -> Self {
let mut hmr = HashMapRecord::new();
hmr.insert(networkid.to_string(), record.to_vec());
Self(hmr)
}
pub fn update(&mut self, other_map_record: &MapRecord) {
for (other_nid, other_records) in other_map_record.iter() {
if let Some(cached_records) = self.0.get_mut(other_nid) {
for other_rec in other_records.iter() {
cached_records.push(other_rec.clone());
}
cached_records.sort();
let mut uniq_urls: Vec<String> =
cached_records.iter().map(|rec| rec.url.clone()).collect();
uniq_urls.dedup();
let mut final_records: Vec<Record> = Vec::new();
for url in uniq_urls {
let drain_record_with_url: Vec<Record> = cached_records
.drain_filter(|rec| *rec.url == *url)
.collect();
if drain_record_with_url.len() == 1 {
final_records.push(drain_record_with_url[0].clone());
} else {
if let Some(first_record) = drain_record_with_url.first() {
if first_record.ttl == 0 {
final_records.push(first_record.clone());
}
}
if let Some(last_record) = drain_record_with_url.last() {
if last_record.ttl == 255 {
final_records.push(last_record.clone());
}
}
}
}
*cached_records = final_records.clone();
} else {
self.0.insert(other_nid.to_string(), other_records.to_vec());
}
}
}
pub fn from_dns_message(dmesg: &DnsMessage) -> Option<MapRecord> {
if !dmesg.answers.is_empty() {
let mut records: Vec<Record> = dmesg
.answers
.iter()
.filter_map(|a_sec| {
if a_sec.answer_class == 1 && a_sec.answer_type == 5 {
Some(Record::new(
&a_sec.domain_name,
&a_sec.data.target,
a_sec.ttl,
))
} else {
None
}
})
.collect();
records.sort();
records.dedup_by(|a, b| a.url() == b.url());
let mut map_record = MapRecord(HashMapRecord::with_capacity(records.len()));
for new_record in records.iter() {
let tmp_map_record =
MapRecord::with_record(&new_record.networkid, &[new_record.clone()]);
map_record.update(&tmp_map_record);
}
Some(map_record)
} else {
None
}
}
pub fn to_dns_response_message(&self, networkids: &[&str]) -> Option<DnsMessage> {
let mut answers = Vec::new();
for &netid in networkids.iter() {
if let Some(records) = self.0.get(netid) {
for rec in records {
answers.push(rec.to_answers_section());
}
}
}
if answers.is_empty() {
None
} else {
Some(DnsMessage {
nb_answers: answers.len() as u16,
nb_authority: answers.len() as u16,
answers,
..DnsMessage::default()
})
}
}
pub fn to_dns_message_query(&self, networkids: &[&str]) -> Option<DnsMessage> {
let mut questions = Vec::new();
for &netid in networkids.iter() {
if let Some(records) = self.0.get(netid) {
for rec in records {
questions.push(rec.to_question_section());
}
}
}
if questions.is_empty() {
None
} else {
Some(DnsMessage {
nb_questions: questions.len() as u16,
questions,
..DnsMessage::default()
})
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Record {
pub(crate) networkid: String,
pub(crate) url: String,
pub(crate) ttl: u32,
}
impl Record {
pub fn new(name: &str, url: &str, ttl: u32) -> Self {
Record {
networkid: name.to_owned(),
url: url.to_owned(),
ttl,
}
}
pub fn url(&self) -> &str {
&self.url
}
pub fn networkid(&self) -> &str {
&self.networkid
}
pub fn ttl(&self) -> u32 {
self.ttl
}
pub fn new_own() -> Self {
let hostname = convert_to_mdns_hostname(
&hostname::get_hostname().unwrap_or_else(|| String::from("Anonymous-host")),
);
Record {
networkid: hostname,
url: String::from(DEFAULT_BIND_ADRESS),
ttl: DEFAULT_TTL,
}
}
pub fn to_answers_section(&self) -> AnswerSection {
AnswerSection::new_with_ttl(&self.networkid, &self.to_target(), self.ttl)
}
pub fn to_question_section(&self) -> QuerySection {
QuerySection::new(&self.networkid)
}
pub fn to_target(&self) -> Target {
Target::new(&self.url)
}
pub fn to_dns_response_message(&self) -> DnsMessage {
DnsMessage {
nb_answers: 1,
nb_authority: 1,
answers: vec![self.to_answers_section()],
..DnsMessage::default()
}
}
}
impl Ord for Record {
fn cmp(&self, other: &Self) -> Ordering {
match self.networkid.cmp(&other.networkid) {
Ordering::Equal => match self.url.cmp(&other.url) {
Ordering::Equal => self.ttl.cmp(&other.ttl),
other_ordering => other_ordering,
},
other_ordering => other_ordering,
}
}
}
impl PartialOrd for Record {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn convert_to_mdns_hostname(hostname: &str) -> String {
if hostname.ends_with(".local.") {
hostname.to_string()
} else {
format!("{}.local.", hostname)
}
}
#[test]
fn map_record_update_test() {
let networkid = "hcnmynetworkid.hc-mdns-discovery.holo.host";
let url = "wss://1.2.3.4:12345?a=HcMmynodeid";
let record_to_prune1 = Record::new(networkid, url, 255);
let record_to_prune2 = Record::new(networkid, url, 200);
let record_to_keep = Record::new(networkid, url, 100);
let mut map_record = MapRecord::with_record(
networkid,
&[record_to_prune1, record_to_prune2, record_to_keep.clone()],
);
for (_, records) in map_record.iter_mut() {
records.sort();
records.dedup_by(|a, b| a.url() == b.url());
}
if let Some(dedup_records) = map_record.get(networkid) {
assert_eq!(dedup_records, &[record_to_keep])
}
}