Skip to content
Snippets Groups Projects
route.rs 13.1 KiB
Newer Older
use arrayvec::ArrayVec;

Stephen D's avatar
Stephen D committed
use crate::error::AppendNodeError;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Route {
    pub max_hops: u8,
    path: ArrayVec<u8, 254>,
    has_future: bool,
}

impl Route {
    pub fn new(max_hops: u8) -> Self {
        let path = ArrayVec::new();

        Self {
            max_hops,
            path,
            has_future: false,
        }
    }

    /// Returns `None` if there isn't enough space for the callsign
    /// Returns `None` if attempting to pushed is_used=true after an existing is_used=false
    /// is_future=false when this callsign has digipeated
    /// is_future=true when this callsign is expected to be part of the route, but isn't yet
    /// see the specs for more information
Stephen D's avatar
Stephen D committed
    pub fn push_callsign(&mut self, ident: RouteIdentity) -> Option<()> {
        let len = ident.callsign.as_bytes().len() + 3;
        let free_space = self.path.capacity() - self.path.len();

        if len > free_space {
            return None;
        }

Stephen D's avatar
Stephen D committed
        self.has_future = self.has_future || ident.is_future;
        if self.has_future && !ident.is_future {
            return None;
        }

        // safe to unwrap since we already did a length check
        self.path
Stephen D's avatar
Stephen D committed
            .try_extend_from_slice(ident.callsign.as_bytes())
            .unwrap();
Stephen D's avatar
Stephen D committed
        self.path.push(if ident.is_future { 0xFD } else { 0xFF });
        self.path.push(ident.ssid);
        self.path.push(ident.rssi);

        Some(())
    }

    /// Returns `None` if there isn't enough space
    pub fn push_internet(&mut self) -> Option<()> {
        let free_space = self.path.capacity() - self.path.len();

        if free_space < 1 {
            return None;
        }

        self.path.push(0xFE);

        Some(())
    }

Stephen D's avatar
Stephen D committed
    /// Append a callsign/ssid pair to the route, intelligently.
    /// I.e. replace future node if possible
    /// Returns an Err if the route is out of space, or appending the node doesn't make logical sense
    /// (there's a future node that doesn't match)
Stephen D's avatar
Stephen D committed
    pub fn append_node(
        &mut self,
        callsign: &str,
        ssid: u8,
        rssi: Option<f64>,
    ) -> Result<(), AppendNodeError> {
Stephen D's avatar
Stephen D committed
        let mut new_route = Route::new(self.max_hops);

        let mut already_inserted = false;
        for rn in self.iter() {
            match rn {
                RouteNode::Internet => new_route
                    .push_internet()
                    .ok_or(AppendNodeError::RouteOverflow)?,
Stephen D's avatar
Stephen D committed
                RouteNode::Identity(ident) => {
                    let us = ident.callsign() == callsign && ident.ssid() == ssid;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
                    if ident.is_future() {
Stephen D's avatar
Stephen D committed
                        if us {
                            if already_inserted {
                                return Err(AppendNodeError::DuplicateNode);
                            } else {
Stephen D's avatar
Stephen D committed
                                new_route
                                    .push_callsign(RouteIdentity::new(callsign, ssid, rssi, false));
Stephen D's avatar
Stephen D committed
                                already_inserted = true;
                            }
Stephen D's avatar
Stephen D committed
                        } else if already_inserted {
                            new_route.push_callsign(ident);
Stephen D's avatar
Stephen D committed
                        } else {
Stephen D's avatar
Stephen D committed
                            return Err(AppendNodeError::SetFuture);
Stephen D's avatar
Stephen D committed
                        }
Stephen D's avatar
Stephen D committed
                    } else if us {
                        return Err(AppendNodeError::DuplicateNode);
Stephen D's avatar
Stephen D committed
                    } else {
Stephen D's avatar
Stephen D committed
                        new_route.push_callsign(ident);
Stephen D's avatar
Stephen D committed
                    }
                }
            }
        }

        if !already_inserted {
Stephen D's avatar
Stephen D committed
            new_route.push_callsign(RouteIdentity::new(callsign, ssid, rssi, false));
Stephen D's avatar
Stephen D committed
        }

        if new_route.iter().count() > usize::from(new_route.max_hops) {
            return Err(AppendNodeError::HopsOverflow);
Stephen D's avatar
Stephen D committed
        }

        *self = new_route;
        Ok(())
Stephen D's avatar
Stephen D committed
    }

    pub fn iter(&'_ self) -> RouteIter<'_> {
        RouteIter::new(self)
    }

    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        let packet_len = self.path.len() + 1;
        let buf = buf.get_mut(0..(packet_len + 1))?;
        buf[0] = packet_len.try_into().unwrap();
        buf[1] = self.max_hops;
        buf[2..(packet_len + 1)].copy_from_slice(&self.path);

        Some(buf)
    }

    pub fn decode(data: &[u8]) -> Option<Self> {
        let len: usize = (*data.first()?).into();
        let data = data.get(1..(len + 1))?;

        let max_hops = *data.first()?;

        let mut path = ArrayVec::new();
        path.try_extend_from_slice(&data[1..]).unwrap();

        let has_future = data[1..].iter().any(|x| *x == 0xFD);

            max_hops,
            path,
            has_future,
        if UntrustedRouteIter::new(&s).any(|v| v.is_err()) {
    }
}

#[derive(Debug, Eq, PartialEq)]
pub enum RouteNode<'a> {
    Internet,
Stephen D's avatar
Stephen D committed
    Identity(RouteIdentity<'a>),
}

#[derive(Debug, Eq, PartialEq)]
pub struct RouteIdentity<'a> {
    callsign: &'a str,
    ssid: u8,
    rssi: u8,
    is_future: bool,
}

impl<'a> RouteIdentity<'a> {
    pub fn new(callsign: &'a str, ssid: u8, rssi: Option<f64>, is_future: bool) -> Self {
        let rssi = match rssi {
            Some(rssi) => (rssi * 1.5 + 240.0).max(1.0) as u8,
            None => 0,
        };

        Self {
            callsign,
            ssid,
            rssi,
            is_future,
        }
    }

    pub fn callsign(&self) -> &'a str {
        self.callsign
    }

    pub fn ssid(&self) -> u8 {
        self.ssid
    }

    pub fn rssi(&self) -> Option<f64> {
        if self.rssi == 0 {
            None
        } else {
            Some(((self.rssi as f64) - 240.0) / 1.5)
        }
    }

    pub fn is_future(&self) -> bool {
        self.is_future
    }
    route: &'a Route,
    i: usize,
    fn new(route: &'a Route) -> Self {
    // Returns Err(()) if invalid
    fn maybe_next(&mut self) -> Result<RouteNode<'a>, ()> {
        let i_start = self.i;
        self.i += 1;

        if *self.route.path.get(i_start).ok_or(())? == 0xFE {
            return Ok(RouteNode::Internet);
        while *self.route.path.get(self.i).ok_or(())? != 0xFD && self.route.path[self.i] != 0xFF {
        let callsign = core::str::from_utf8(self.route.path.get(i_start..self.i).ok_or(())?)
            .map_err(|_| ())?;
        let is_future = *self.route.path.get(self.i).ok_or(())? == 0xFD;
        if self.seen_future && !is_future {
            // past after future - not allowed
            return Err(());
Stephen D's avatar
Stephen D committed
        let ssid = *self.route.path.get(self.i + 1).ok_or(())?;
        let rssi = *self.route.path.get(self.i + 2).ok_or(())?;
        self.i += 3;
Stephen D's avatar
Stephen D committed
        Ok(RouteNode::Identity(RouteIdentity {
            callsign,
            ssid,
            rssi,
            is_future,
        }))
    type Item = Result<RouteNode<'a>, ()>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.dead {
            return None;
        }

        if self.i == self.route.path.len() {
            return None;
        }

        let r = self.maybe_next();

        if r.is_err() {
            self.dead = true;
    }
}

pub struct RouteIter<'a> {
    iter: UntrustedRouteIter<'a>,
}

impl<'a> RouteIter<'a> {
    fn new(route: &'a Route) -> Self {
        Self {
            iter: UntrustedRouteIter::new(route),
        }
    }
}

impl<'a> Iterator for RouteIter<'a> {
    type Item = RouteNode<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        Some(self.iter.next()?.unwrap())
Stephen D's avatar
Stephen D committed

#[cfg(test)]
mod tests {
    use super::*;

Stephen D's avatar
Stephen D committed
    #[test]
    fn route_rssi() {
        let x = -66.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        assert_eq!(x, r.rssi().unwrap());

        let x = 10.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        assert_eq!(x, r.rssi().unwrap());

        let x = -158.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        assert_eq!(x, r.rssi().unwrap());

        let r = RouteIdentity::new("C0", 23, None, false);
        assert_eq!(None, r.rssi());
    }

Stephen D's avatar
Stephen D committed
    #[test]
    fn append_fails_when_existing_future() {
        let mut r = Route::new(5);
Stephen D's avatar
Stephen D committed
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
Stephen D's avatar
Stephen D committed

        assert_eq!(
            AppendNodeError::SetFuture,
Stephen D's avatar
Stephen D committed
            r.append_node("C3", 1, None).unwrap_err()
Stephen D's avatar
Stephen D committed
        );

        assert_eq!(
            AppendNodeError::SetFuture,
Stephen D's avatar
Stephen D committed
            r.append_node("C4", 0, None).unwrap_err()
Stephen D's avatar
Stephen D committed
        );
    }

    #[test]
    fn append_fails_when_would_exceed_max_hops() {
        let mut r = Route::new(3);
Stephen D's avatar
Stephen D committed
        r.append_node("C1", 0, None).unwrap();
        r.append_node("C2", 0, None).unwrap();
        r.append_node("C2", 1, None).unwrap();
Stephen D's avatar
Stephen D committed
        assert_eq!(
            AppendNodeError::HopsOverflow,
Stephen D's avatar
Stephen D committed
            r.append_node("C4", 0, None).unwrap_err()
Stephen D's avatar
Stephen D committed
        );
    }

    #[test]
    fn append_fails_when_already_in_route() {
        let mut r = Route::new(3);
Stephen D's avatar
Stephen D committed
        r.append_node("C1", 0, None).unwrap();
Stephen D's avatar
Stephen D committed

        assert_eq!(
            AppendNodeError::DuplicateNode,
Stephen D's avatar
Stephen D committed
            r.append_node("C1", 0, None).unwrap_err()
Stephen D's avatar
Stephen D committed
        );
    }

    #[test]
    fn append_overwrites_future() {
        let mut r = Route::new(5);
Stephen D's avatar
Stephen D committed
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C4", 0, None, true))
            .unwrap();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        r.append_node("C3", 0, None).unwrap();

        let mut iter = r.iter();
Stephen D's avatar
Stephen D committed
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C1", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C2", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C3", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new("C4", 0, None, true))),
            iter.next()
        );
        assert_eq!(None, iter.next());
    }

    #[test]
    fn append_overwrites_future_even_when_at_hop_limit() {
        let mut r = Route::new(4);
Stephen D's avatar
Stephen D committed
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C4", 0, None, true))
            .unwrap();
Stephen D's avatar
Stephen D committed
        r.append_node("C3", 0, None).unwrap();
Stephen D's avatar
Stephen D committed

        let mut iter = r.iter();
Stephen D's avatar
Stephen D committed
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C1", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C2", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C3", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new("C4", 0, None, true))),
            iter.next()
        );
Stephen D's avatar
Stephen D committed
        assert_eq!(None, iter.next());
    }

    #[test]
    fn append_appends_when_no_future() {
        let mut r = Route::new(5);
Stephen D's avatar
Stephen D committed
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, false))
            .unwrap();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        r.append_node("C4", 0, None).unwrap();
Stephen D's avatar
Stephen D committed

        let mut iter = r.iter();
Stephen D's avatar
Stephen D committed
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C1", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C2", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C3", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C4", 0, None, false
            ))),
            iter.next()
        );
Stephen D's avatar
Stephen D committed
        assert_eq!(None, iter.next());
    }
}