diff --git a/src/error.rs b/src/error.rs
index 65b760adf64a0379192f52b583112878d9923202..92038ac18d5b9c29335b5aebf2b017e4cf1dc610 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -41,3 +41,48 @@ pub enum CommentError {
     #[snafu(display("Given buffer too small for comment data"))]
     BufferOverflow,
 }
+
+#[derive(Debug, Snafu)]
+pub enum DigipeatError {
+    #[snafu(display("No route"))]
+    NoRoute,
+
+    #[snafu(display("Identification is us"))]
+    Us,
+
+    #[snafu(display("Max hops hit"))]
+    MaxHops,
+
+    #[snafu(display("Already digipeated by this node"))]
+    AlreadyDigipeated,
+
+    #[snafu(display("Future hop(s) are already set"))]
+    SetDestiny,
+}
+
+#[derive(Debug, Snafu, PartialEq, Eq)]
+pub enum AppendNodeError {
+    #[snafu(display("Future hop(s) already set to a different node"))]
+    SetFuture,
+
+    #[snafu(display("Max hops hit"))]
+    HopsOverflow,
+
+    #[snafu(display("Node already in route"))]
+    DuplicateNode,
+
+    #[snafu(display("Given data causes the route whisker to overflow"))]
+    RouteOverflow,
+}
+
+#[derive(Debug, Snafu, PartialEq, Eq)]
+pub enum PacketRouteAppendError {
+    #[snafu(display("No route whisker on packet"))]
+    NoRouteWhisker,
+
+    #[snafu(display("Route error"))]
+    Route { error: AppendNodeError },
+
+    #[snafu(display("CATS packet overflow"))]
+    PacketOverflow,
+}
diff --git a/src/packet.rs b/src/packet.rs
index 093362873f9f4bcf746056b3d3a527cd27bac075..bb43dce53d35bc075f710797facf2738477faaaf 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -4,10 +4,10 @@ use crc::{Crc, CRC_16_IBM_SDLC};
 
 use crate::{
     buffer::{Buffer, BufferOverflow},
-    error::{CommentError, DecodeError, EncodeError},
+    error::{CommentError, DecodeError, DigipeatError, EncodeError, PacketRouteAppendError},
     interleaver, ldpc, utf8,
     whisker::{
-        Arbitrary, Comment, Destination, Gps, Identification, Route, Timestamp,
+        Arbitrary, Comment, Destination, Gps, Identification, Route, RouteNode, Timestamp,
         ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE,
     },
     whitener,
@@ -271,6 +271,75 @@ impl<'a, const N: usize> Packet<'a, N> {
         self.clear_by_type(COMMENT_TYPE, true);
     }
 
+    /// Given the callsign and ssid of a node, should it digipeat this packet?
+    /// Takes into account things such as if we've digipeated it already, the max hops, etc.
+    pub fn should_digipeat(&self, callsign: &str, ssid: u8) -> Result<(), DigipeatError> {
+        let route = match self.route() {
+            Some(x) => x,
+            None => {
+                return Err(DigipeatError::NoRoute);
+            }
+        };
+
+        if let Some(ident) = self.identification() {
+            if &ident.callsign == callsign && ident.ssid == ssid {
+                return Err(DigipeatError::Us);
+            }
+        }
+
+        let max_hops: usize = route.max_hops.into();
+        let cur_hops = route
+            .iter()
+            .filter(|r| match r {
+                RouteNode::Internet => false,
+                RouteNode::Identity(_, _, is_future) => !is_future,
+            })
+            .count();
+
+        if max_hops <= cur_hops {
+            return Err(DigipeatError::MaxHops);
+        }
+
+        let already_digipeated = route.iter().any(|r| match r {
+            RouteNode::Internet => false,
+            RouteNode::Identity(rc, rs, is_future) => rc == callsign && rs == ssid && !is_future,
+        });
+
+        if already_digipeated {
+            return Err(DigipeatError::AlreadyDigipeated);
+        }
+
+        let next_node = route.iter().find_map(|r| match r {
+            RouteNode::Identity(c, s, is_future) if is_future => Some((c, s)),
+            _ => None,
+        });
+
+        match next_node {
+            Some((rc, rs)) if rc != callsign || rs != ssid => {
+                return Err(DigipeatError::SetDestiny)
+            }
+            _ => Ok(()),
+        }
+    }
+
+    /// Note that if this fails due to a CATS overflow, it will wipe the route off of the packet
+    pub fn append_to_route(
+        &mut self,
+        callsign: &str,
+        ssid: u8,
+    ) -> Result<(), PacketRouteAppendError> {
+        let mut route = self.route().ok_or(PacketRouteAppendError::NoRouteWhisker)?;
+        route
+            .append_node(callsign, ssid)
+            .map_err(|error| PacketRouteAppendError::Route { error })?;
+        self.clear_route();
+
+        self.add_route(route)
+            .map_err(|_| PacketRouteAppendError::PacketOverflow)?;
+
+        Ok(())
+    }
+
     fn clear_by_type(&mut self, whisker_type: u8, all: bool) {
         let mut i = 0;
         while i < self.buf.len() {
diff --git a/src/whisker/route.rs b/src/whisker/route.rs
index 1ce625d882b6fad94c388e103dd2fa32728bcbed..89810915547c87677e813003126d5d9c87187278 100644
--- a/src/whisker/route.rs
+++ b/src/whisker/route.rs
@@ -1,5 +1,7 @@
 use arrayvec::ArrayVec;
 
+use crate::error::AppendNodeError;
+
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub struct Route {
     pub max_hops: u8,
@@ -59,6 +61,93 @@ impl Route {
         Some(())
     }
 
+    /// 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)
+    pub fn append_node(&mut self, callsign: &str, ssid: u8) -> Result<(), AppendNodeError> {
+        if usize::from(self.max_hops) == self.iter().count() {
+            return Err(AppendNodeError::HopsOverflow);
+        }
+
+        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)?,
+                RouteNode::Identity(rc, rs, is_future) => {
+                    let us = rc == callsign && rs == ssid;
+
+                    if is_future {
+                        if us {
+                            if already_inserted {
+                                return Err(AppendNodeError::DuplicateNode);
+                            } else {
+                                new_route.push_callsign(callsign, ssid, false);
+                                already_inserted = true;
+                            }
+                        } else {
+                            if already_inserted {
+                                new_route.push_callsign(rc, rs, true);
+                            } else {
+                                return Err(AppendNodeError::SetFuture);
+                            }
+                        }
+                    } else {
+                        if us {
+                            return Err(AppendNodeError::DuplicateNode);
+                        } else {
+                            new_route.push_callsign(rc, rs, false);
+                        }
+                    }
+                }
+            }
+        }
+
+        if !already_inserted {
+            new_route.push_callsign(callsign, ssid, false);
+        }
+
+        *self = new_route;
+        Ok(())
+        /*
+        let replace_future = r.iter().any(|rn| match rn {
+            RouteNode::Identity(rc, rs, is_future) => rc == callsign && rs == ssid && is_future,
+            _ => false,
+        });
+
+        if replace_future {
+            let mut new_route = Route::new(r.max_hops);
+            let mut already_replaced = false;
+
+            for rn in r.iter() {
+                match rn {
+                    RouteNode::Identity(rc, rs, is_future)
+                        if rc == callsign && rs == ssid && is_future && !already_replaced =>
+                    {
+                        already_replaced = true;
+                        new_route.push_callsign(callsign, ssid, false)?;
+                    }
+                    RouteNode::Identity(rc, rs, is_future) => {
+                        new_route.push_callsign(rc, rs, is_future)?;
+                    }
+                    RouteNode::Internet => {
+                        new_route.push_internet()?;
+                    }
+                }
+            }
+
+            *r = new_route;
+        } else {
+            r.push_callsign(callsign, ssid, false)?;
+        }
+
+        Some(())*/
+    }
+
     pub fn iter(&'_ self) -> RouteIter<'_> {
         RouteIter::new(self)
     }
@@ -194,3 +283,84 @@ impl<'a> Iterator for RouteIter<'a> {
         Some(self.iter.next()?.unwrap())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn append_fails_when_existing_future() {
+        let mut r = Route::new(5);
+        r.push_callsign("C1", 0, false);
+        r.push_callsign("C2", 0, false);
+        r.push_callsign("C3", 0, true);
+
+        assert_eq!(
+            AppendNodeError::SetFuture,
+            r.append_node("C3", 1).unwrap_err()
+        );
+
+        assert_eq!(
+            AppendNodeError::SetFuture,
+            r.append_node("C4", 0).unwrap_err()
+        );
+    }
+
+    #[test]
+    fn append_fails_when_would_exceed_max_hops() {
+        let mut r = Route::new(3);
+        r.append_node("C1", 0).unwrap();
+        r.append_node("C2", 0).unwrap();
+        r.append_node("C2", 1).unwrap();
+        assert_eq!(
+            AppendNodeError::HopsOverflow,
+            r.append_node("C4", 0).unwrap_err()
+        );
+    }
+
+    #[test]
+    fn append_fails_when_already_in_route() {
+        let mut r = Route::new(3);
+        r.append_node("C1", 0).unwrap();
+
+        assert_eq!(
+            AppendNodeError::DuplicateNode,
+            r.append_node("C1", 0).unwrap_err()
+        );
+    }
+
+    #[test]
+    fn append_overwrites_future() {
+        let mut r = Route::new(5);
+        r.push_callsign("C1", 0, false);
+        r.push_callsign("C2", 0, false);
+        r.push_callsign("C3", 0, true);
+        r.push_callsign("C4", 0, true);
+
+        r.append_node("C3", 0).unwrap();
+
+        let mut iter = r.iter();
+        assert_eq!(Some(RouteNode::Identity("C1", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C2", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C3", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C4", 0, true)), iter.next());
+        assert_eq!(None, iter.next());
+    }
+
+    #[test]
+    fn append_appends_when_no_future() {
+        let mut r = Route::new(5);
+        r.push_callsign("C1", 0, false);
+        r.push_callsign("C2", 0, false);
+        r.push_callsign("C3", 0, false);
+
+        r.append_node("C4", 0).unwrap();
+
+        let mut iter = r.iter();
+        assert_eq!(Some(RouteNode::Identity("C1", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C2", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C3", 0, false)), iter.next());
+        assert_eq!(Some(RouteNode::Identity("C4", 0, false)), iter.next());
+        assert_eq!(None, iter.next());
+    }
+}