uf_crsf/packets/
game.rs

1use crate::packets::{CrsfPacket, PacketType};
2use crate::CrsfParsingError;
3use core::mem::size_of;
4
5const ADD_POINTS_SUB_TYPE: u8 = 0x01;
6const COMMAND_CODE_SUB_TYPE: u8 = 0x02;
7
8/// Represents a Game packet (frame type 0x3C).
9#[derive(Clone, Debug, PartialEq)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct Game {
12    pub dst_addr: u8,
13    pub src_addr: u8,
14    pub payload: GamePayload,
15}
16
17impl Game {
18    pub fn new(dst_addr: u8, src_addr: u8, payload: GamePayload) -> Result<Self, CrsfParsingError> {
19        Ok(Self {
20            dst_addr,
21            src_addr,
22            payload,
23        })
24    }
25}
26
27/// Enum for the different payloads of a Game packet.
28#[derive(Clone, Debug, PartialEq)]
29#[cfg_attr(feature = "defmt", derive(defmt::Format))]
30pub enum GamePayload {
31    AddPoints(i16),
32    CommandCode(u16),
33}
34
35impl CrsfPacket for Game {
36    const PACKET_TYPE: PacketType = PacketType::Game;
37    // Dst + Src + Sub-type + max payload size (i16/u16)
38    const MIN_PAYLOAD_SIZE: usize = 3 * size_of::<u8>() + size_of::<u16>();
39
40    fn from_bytes(data: &[u8]) -> Result<Self, CrsfParsingError> {
41        if data.len() < Self::MIN_PAYLOAD_SIZE {
42            return Err(CrsfParsingError::InvalidPayloadLength);
43        }
44
45        let dst_addr = data[0];
46        let src_addr = data[1];
47        let sub_type = data[2];
48        let sub_payload = &data[3..5];
49
50        let payload = match sub_type {
51            ADD_POINTS_SUB_TYPE => GamePayload::AddPoints(i16::from_be_bytes(
52                sub_payload[0..size_of::<i16>()]
53                    .try_into()
54                    .expect("infallible due to length check"),
55            )),
56            COMMAND_CODE_SUB_TYPE => GamePayload::CommandCode(u16::from_be_bytes(
57                sub_payload[0..size_of::<u16>()]
58                    .try_into()
59                    .expect("infallible due to length check"),
60            )),
61            _ => return Err(CrsfParsingError::InvalidPayload), // Unknown sub-type
62        };
63
64        Ok(Self {
65            dst_addr,
66            src_addr,
67            payload,
68        })
69    }
70
71    fn to_bytes(&self, buffer: &mut [u8]) -> Result<usize, CrsfParsingError> {
72        let (sub_type, payload_bytes) = match &self.payload {
73            GamePayload::AddPoints(points) => (ADD_POINTS_SUB_TYPE, points.to_be_bytes()),
74            GamePayload::CommandCode(code) => (COMMAND_CODE_SUB_TYPE, code.to_be_bytes()),
75        };
76
77        let payload_len = payload_bytes.len();
78        let total_len = 2 + 1 + payload_len;
79
80        if buffer.len() < total_len {
81            return Err(CrsfParsingError::BufferOverflow);
82        }
83
84        buffer[0] = self.dst_addr;
85        buffer[1] = self.src_addr;
86        buffer[2] = sub_type;
87        buffer[3..3 + payload_len].copy_from_slice(&payload_bytes);
88
89        Ok(total_len)
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_add_points_from_bytes() {
99        let data: [u8; 5] = [0xEA, 0xEE, ADD_POINTS_SUB_TYPE, 0x00, 0x64]; // 100 points
100        let packet = Game::from_bytes(&data).unwrap();
101        assert_eq!(packet.dst_addr, 0xEA);
102        assert_eq!(packet.src_addr, 0xEE);
103        assert!(matches!(packet.payload, GamePayload::AddPoints(points) if points ==  100));
104    }
105
106    #[test]
107    fn test_add_points_to_bytes() {
108        let packet = Game {
109            dst_addr: 0xEA,
110            src_addr: 0xEE,
111            payload: GamePayload::AddPoints(100),
112        };
113        let mut buffer = [0u8; 5];
114        let len = packet.to_bytes(&mut buffer).unwrap();
115        assert_eq!(len, 5);
116        let expected: [u8; 5] = [0xEA, 0xEE, ADD_POINTS_SUB_TYPE, 0x00, 0x64];
117        assert_eq!(buffer, expected);
118    }
119
120    #[test]
121    fn test_command_code_from_bytes() {
122        let data: [u8; 5] = [0xC8, 0xEC, COMMAND_CODE_SUB_TYPE, 0x12, 0x34]; // code 0x1234
123        let packet = Game::from_bytes(&data).unwrap();
124        assert_eq!(packet.dst_addr, 0xC8);
125        assert_eq!(packet.src_addr, 0xEC);
126        assert!(matches!(packet.payload, GamePayload::CommandCode(code) if code ==  0x1234));
127    }
128
129    #[test]
130    fn test_command_code_to_bytes() {
131        let packet = Game {
132            dst_addr: 0xC8,
133            src_addr: 0xEC,
134            payload: GamePayload::CommandCode(0x1234),
135        };
136        let mut buffer = [0u8; 5];
137        let len = packet.to_bytes(&mut buffer).unwrap();
138        assert_eq!(len, 5);
139        let expected: [u8; 5] = [0xC8, 0xEC, COMMAND_CODE_SUB_TYPE, 0x12, 0x34];
140        assert_eq!(buffer, expected);
141    }
142
143    #[test]
144    fn test_game_round_trip() {
145        let packet = Game {
146            dst_addr: 0xC8,
147            src_addr: 0xEC,
148            payload: GamePayload::AddPoints(-50),
149        };
150        let mut buffer = [0u8; 5];
151        packet.to_bytes(&mut buffer).unwrap();
152        let round_trip = Game::from_bytes(&buffer).unwrap();
153        assert_eq!(packet, round_trip);
154    }
155
156    #[test]
157    fn test_game_from_bytes_too_small() {
158        let data: [u8; 4] = [0; 4];
159        let result = Game::from_bytes(&data);
160        assert_eq!(result, Err(CrsfParsingError::InvalidPayloadLength));
161    }
162
163    #[test]
164    fn test_game_to_bytes_too_small() {
165        let packet = Game {
166            dst_addr: 0xC8,
167            src_addr: 0xEC,
168            payload: GamePayload::AddPoints(-50),
169        };
170        let mut buffer = [0u8; 4];
171        let result = packet.to_bytes(&mut buffer);
172        assert_eq!(result, Err(CrsfParsingError::BufferOverflow));
173    }
174}