1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::{
    collections::HashMap,
    net::{IpAddr, SocketAddr},
};

use crate::conversion_utils::{hashmap_to_txt, ip_addr_to_resource_record, port_to_srv_record};
use simple_dns::{Name, ResourceRecord};

/// Represents a single instance of the service.
/// Notice that it is not possible to associate a port to a single ip address, due to limitations of the DNS protocol
#[derive(Debug)]
pub struct InstanceInformation {
    /// Ips for this instance
    pub ip_addresses: Vec<IpAddr>,
    /// Ports for this instance
    pub ports: Vec<u16>,
    /// Attributes for this instance
    pub attributes: HashMap<String, Option<String>>,
}

impl Default for InstanceInformation {
    fn default() -> Self {
        Self::new()
    }
}

impl InstanceInformation {
    /// Creates an empty InstanceInformation
    pub fn new() -> Self {
        Self {
            ip_addresses: Vec::new(),
            ports: Vec::new(),
            attributes: HashMap::new(),
        }
    }

    pub(crate) fn from_records<'a>(records: impl Iterator<Item = &'a ResourceRecord<'a>>) -> Self {
        let mut ip_addresses: Vec<IpAddr> = Vec::new();
        let mut ports = Vec::new();
        let mut attributes = HashMap::new();

        for resource in records {
            match &resource.rdata {
                simple_dns::rdata::RData::A(a) => {
                    ip_addresses.push(std::net::Ipv4Addr::from(a.address).into())
                }
                simple_dns::rdata::RData::AAAA(aaaa) => {
                    ip_addresses.push(std::net::Ipv6Addr::from(aaaa.address).into())
                }
                simple_dns::rdata::RData::TXT(txt) => attributes.extend(txt.attributes()),
                simple_dns::rdata::RData::SRV(srv) => ports.push(srv.port),
                _ => {}
            }
        }

        InstanceInformation {
            ip_addresses,
            ports,
            attributes,
        }
    }

    /// Transform into a [Vec<ResourceRecord>](`Vec<ResourceRecord>`)
    pub fn into_records<'a>(
        self,
        service_name: &Name<'a>,
        ttl: u32,
    ) -> Result<Vec<ResourceRecord<'a>>, crate::SimpleMdnsError> {
        let mut records = Vec::new();

        for ip_address in self.ip_addresses {
            records.push(ip_addr_to_resource_record(service_name, ip_address, ttl));
        }

        for port in self.ports {
            records.push(port_to_srv_record(service_name, port, ttl));
        }

        records.push(hashmap_to_txt(service_name, self.attributes, ttl)?);

        Ok(records)
    }

    /// Creates a Iterator of [`SocketAddr`](`std::net::SocketAddr`) for each ip address and port combination
    pub fn get_socket_addresses(&'_ self) -> impl Iterator<Item = SocketAddr> + '_ {
        self.ip_addresses.iter().copied().flat_map(move |addr| {
            self.ports
                .iter()
                .copied()
                .map(move |port| SocketAddr::new(addr, port))
        })
    }
}

impl std::hash::Hash for InstanceInformation {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.ip_addresses.hash(state);
        self.ports.hash(state);
    }
}

impl From<SocketAddr> for InstanceInformation {
    fn from(addr: SocketAddr) -> Self {
        let ip_address = addr.ip();
        let port = addr.port();

        Self {
            ip_addresses: vec![ip_address],
            ports: vec![port],
            attributes: HashMap::new(),
        }
    }
}