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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Contains the AMS NetId and related types.

use std::convert::TryInto;
use std::fmt::{self, Display};
use std::io::{Read, Write};
use std::net::Ipv4Addr;
use std::str::FromStr;

use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use itertools::Itertools;
use zerocopy::{AsBytes, FromBytes};

/// Represents an AMS NetID.
///
/// The NetID consists of 6 bytes commonly written like an IPv4 address, i.e.
/// `1.2.3.4.5.6`. Together with an AMS port (16-bit integer), it uniquely
/// identifies an endpoint of an ADS system that can be communicated with.
///
/// Although often the first 4 bytes of a NetID look like an IP address, and
/// sometimes even are identical to the device's IP address, there is no
/// requirement for this, and one should never rely on it.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Debug, AsBytes, FromBytes)]
#[repr(C)]
pub struct AmsNetId(pub [u8; 6]);

/// An AMS port is, similar to an IP port, a 16-bit integer.
pub type AmsPort = u16;

impl AmsNetId {
    /// Create a NetID from six bytes.
    pub const fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> Self {
        AmsNetId([a, b, c, d, e, f])
    }

    /// Create a NetID from a slice (which must have length 6).
    pub fn from_slice(slice: &[u8]) -> Option<Self> {
        Some(AmsNetId(slice.try_into().ok()?))
    }

    /// Create a NetID from an IPv4 address and two additional octets.
    pub fn from_ip(ip: Ipv4Addr, e: u8, f: u8) -> Self {
        let [a, b, c, d] = ip.octets();
        Self::new(a, b, c, d, e, f)
    }

    /// Check if the NetID is all-zero.
    pub fn is_zero(&self) -> bool {
        self.0 == [0, 0, 0, 0, 0, 0]
    }
}

impl FromStr for AmsNetId {
    type Err = &'static str;

    /// Parse a NetID from a string (`a.b.c.d.e.f`).
    ///
    /// Bytes can be missing in the end; missing bytes are substituted by 1.
    fn from_str(s: &str) -> Result<AmsNetId, &'static str> {
        let mut arr = [1; 6];
        for (i, part) in s.split('.').enumerate() {
            match (arr.get_mut(i), part.parse()) {
                (Some(loc), Ok(byte)) => *loc = byte,
                _ => return Err("invalid NetID string"),
            }
        }
        Ok(AmsNetId(arr))
    }
}

impl From<[u8; 6]> for AmsNetId {
    fn from(array: [u8; 6]) -> Self {
        Self(array)
    }
}

impl Display for AmsNetId {
    /// Format a NetID in the usual format.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0.iter().format("."))
    }
}

/// Combination of an AMS NetID and a port.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct AmsAddr(AmsNetId, AmsPort);

impl AmsAddr {
    /// Create a new address from NetID and port.
    pub const fn new(netid: AmsNetId, port: AmsPort) -> Self {
        Self(netid, port)
    }

    /// Return the NetID of this address.
    pub const fn netid(&self) -> AmsNetId {
        self.0
    }

    /// Return the port of this address.
    pub const fn port(&self) -> AmsPort {
        self.1
    }

    /// Write the NetID to a stream.
    pub fn write_to<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
        w.write_all(&(self.0).0)?;
        w.write_u16::<LE>(self.1)
    }

    /// Read the NetID from a stream.
    pub fn read_from<R: Read>(r: &mut R) -> std::io::Result<Self> {
        let mut netid = [0; 6];
        r.read_exact(&mut netid)?;
        let port = r.read_u16::<LE>()?;
        Ok(Self(AmsNetId(netid), port))
    }
}

impl FromStr for AmsAddr {
    type Err = &'static str;

    /// Parse an AMS address from a string (netid:port).
    fn from_str(s: &str) -> Result<AmsAddr, &'static str> {
        let (addr, port) = s.split(':').collect_tuple()
                                       .ok_or("invalid AMS addr string")?;
        Ok(Self(addr.parse()?, port.parse().map_err(|_| "invalid port number")?))
    }
}

impl Display for AmsAddr {
    /// Format an AMS address in the usual format.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}:{}", self.0, self.1)
    }
}