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#[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#[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 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), };
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]; 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]; 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}