Skip to main content

tailtalk_packets/
ethertalk.rs

1// Implementation of EtherTalk Phase II frames using 802.2 LLC + SNAP
2use thiserror::Error;
3
4// EtherTalk frames can be one of two types - Aarp, which has an OUI of 0 and a protocol ID of
5// 0x80F3, or DDP with an OUI of 08:00:07 and protocol ID of 0x809B.
6#[derive(Debug, PartialEq, Eq)]
7pub enum EtherTalkPhase2Type {
8    Aarp,
9    Ddp,
10}
11
12#[derive(Debug)]
13pub struct EtherTalkPhase2Frame {
14    pub dst_mac: [u8; 6],
15    pub src_mac: [u8; 6],
16    pub len: u16,
17    pub protocol: EtherTalkPhase2Type,
18}
19
20#[derive(Error, Debug)]
21pub enum EtherTalkError {
22    #[error("invalid size - expected frame to be at least 20 bytes, but found {found:?}")]
23    InvalidSize { found: usize },
24    #[error("not a SNAP frame")]
25    NotSNAP,
26    #[error("unknown OUI+protocol ID")]
27    UnknownHeader,
28}
29
30impl EtherTalkPhase2Frame {
31    pub const LLC_LEN: usize = 8;
32    const FRAME_LEN: usize = 22;
33    const SNAP_MARKER: [u8; 3] = [0xAA, 0xAA, 0x03];
34    const AARP_OUI: [u8; 5] = [0x00, 0x00, 0x00, 0x80, 0xF3];
35    const DDP_OUI: [u8; 5] = [0x08, 0x00, 0x07, 0x80, 0x9B];
36    const DST_MAC_OFF: usize = 0;
37    const SRC_MAC_OFF: usize = 6;
38    const MAC_LEN: usize = 6;
39    const LEN_OFF: usize = 12;
40    const SNAP_OFF: usize = 14;
41    const OUI_OFF: usize = 17;
42
43    pub fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, EtherTalkError> {
44        assert!(buf.len() >= Self::FRAME_LEN);
45
46        buf[Self::DST_MAC_OFF..(Self::DST_MAC_OFF + self.dst_mac.len())]
47            .copy_from_slice(&self.dst_mac);
48        buf[Self::SRC_MAC_OFF..(Self::SRC_MAC_OFF + self.src_mac.len())]
49            .copy_from_slice(&self.src_mac);
50        buf[Self::LEN_OFF..(Self::LEN_OFF + 2)].copy_from_slice(&u16::to_be_bytes(self.len));
51
52        // Signifies that this is a SNAP frame
53        buf[Self::SNAP_OFF..(Self::SNAP_OFF + Self::SNAP_MARKER.len())]
54            .copy_from_slice(&Self::SNAP_MARKER);
55
56        match self.protocol {
57            EtherTalkPhase2Type::Aarp => {
58                buf[Self::OUI_OFF..(Self::OUI_OFF + Self::AARP_OUI.len())]
59                    .copy_from_slice(&Self::AARP_OUI);
60                Ok(Self::FRAME_LEN)
61            }
62            EtherTalkPhase2Type::Ddp => {
63                buf[Self::OUI_OFF..(Self::OUI_OFF + Self::DDP_OUI.len())]
64                    .copy_from_slice(&Self::DDP_OUI);
65                Ok(Self::FRAME_LEN)
66            }
67        }
68    }
69
70    pub const fn len() -> usize {
71        Self::FRAME_LEN
72    }
73
74    pub fn parse(buf: &[u8]) -> Result<Self, EtherTalkError> {
75        use EtherTalkError::*;
76
77        if buf.len() < Self::FRAME_LEN {
78            return Err(InvalidSize { found: buf.len() });
79        } else if buf[Self::SNAP_OFF..(Self::SNAP_OFF + Self::SNAP_MARKER.len())]
80            != Self::SNAP_MARKER
81        {
82            return Err(NotSNAP);
83        }
84
85        let dst_mac = &buf[Self::DST_MAC_OFF..(Self::DST_MAC_OFF + Self::MAC_LEN)];
86        let src_mac = &buf[Self::SRC_MAC_OFF..(Self::SRC_MAC_OFF + Self::MAC_LEN)];
87
88        if buf[Self::OUI_OFF..(Self::OUI_OFF + Self::AARP_OUI.len())] == Self::AARP_OUI {
89            return Ok(Self {
90                dst_mac: *dst_mac.as_array().unwrap(),
91                src_mac: *src_mac.as_array().unwrap(),
92                len: 10,
93                protocol: EtherTalkPhase2Type::Aarp,
94            });
95        } else if buf[Self::OUI_OFF..(Self::OUI_OFF + Self::DDP_OUI.len())] == Self::DDP_OUI {
96            return Ok(Self {
97                dst_mac: *dst_mac.as_array().unwrap(),
98                src_mac: *src_mac.as_array().unwrap(),
99                len: 10,
100                protocol: EtherTalkPhase2Type::Ddp,
101            });
102        }
103
104        Err(UnknownHeader)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use assert_hex::assert_eq_hex;
112
113    #[test]
114    fn test_parse_ethertalk_aarp() {
115        let test_data: &[u8] = &[
116            0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe3, 0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe4, 0x00, 0x04,
117            0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x80, 0xf3, 0x00, 0x01, 0x80, 0x9b, 0x06, 0x04,
118            0x00, 0x03, 0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe3, 0x00, 0xff, 0x1e, 0xf8, 0x00, 0x00,
119            0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1e, 0xf8,
120        ];
121        let dst_mac: [u8; 6] = [0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe3];
122        let src_mac: [u8; 6] = [0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe4];
123
124        let packet = EtherTalkPhase2Frame::parse(test_data).expect("failed to parse");
125
126        assert_eq_hex!(
127            packet.dst_mac,
128            dst_mac,
129            "Destination MAC did not match expected"
130        );
131        assert_eq_hex!(packet.src_mac, src_mac, "Source MAC did not match expected");
132
133        match packet.protocol {
134            EtherTalkPhase2Type::Aarp => {}
135            _ => panic!("parsed as wrong type"),
136        };
137    }
138
139    #[test]
140    fn test_parse_ethertalk_ddp() {
141        let test_data: &[u8] = &[
142            0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe3, 0x00, 0x0c, 0x29, 0x0d, 0x56, 0xe4, 0x00, 0x04,
143            0xaa, 0xaa, 0x03, 0x08, 0x00, 0x07, 0x80, 0x9b, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
144            0xff, 0x1e, 0xff, 0xf8, 0x06, 0x06, 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
145        ];
146
147        let packet = EtherTalkPhase2Frame::parse(test_data).expect("failed to parse");
148
149        if EtherTalkPhase2Type::Ddp != packet.protocol {
150            panic!("parsed as wrong type");
151        }
152    }
153}