1use core::convert::TryFrom;
2
3use crate::{Apci, GroupAddress, IndividualAddress, KnxError, Result};
4
5#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[repr(u8)]
8pub enum CemiMessageCode {
9 LDataRequest = 0x11,
10 LDataIndication = 0x29,
11 LDataConfirmation = 0x2e,
12}
13
14const CEMI_MESSAGE_CODE_ALL: [CemiMessageCode; 3] = [
17 CemiMessageCode::LDataRequest,
18 CemiMessageCode::LDataIndication,
19 CemiMessageCode::LDataConfirmation,
20];
21
22impl CemiMessageCode {
23 pub const fn as_u8(self) -> u8 {
24 self as u8
25 }
26}
27
28impl TryFrom<u8> for CemiMessageCode {
29 type Error = KnxError;
30
31 fn try_from(value: u8) -> Result<Self> {
32 CEMI_MESSAGE_CODE_ALL
33 .iter()
34 .copied()
35 .find(|mc| mc.as_u8() == value)
36 .ok_or(KnxError::InvalidFrame("unsupported cEMI message code"))
37 }
38}
39
40#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct GroupTelegram {
43 source: IndividualAddress,
44 destination: GroupAddress,
45 apci: Apci,
46 payload: std::vec::Vec<u8>,
47}
48
49impl GroupTelegram {
50 pub fn new(
62 source: IndividualAddress,
63 destination: GroupAddress,
64 apci: Apci,
65 payload: &[u8],
66 ) -> Result<Self> {
67 if matches!(apci, Apci::GroupValueRead) && !payload.is_empty() {
68 return Err(KnxError::InvalidFrame("group read cannot carry payload"));
69 }
70 if !matches!(apci, Apci::GroupValueRead) && payload.is_empty() {
71 return Err(KnxError::InvalidFrame(
72 "group write/response must carry payload",
73 ));
74 }
75 if payload.len() > 254 {
76 return Err(KnxError::InvalidFrame("group payload too long"));
77 }
78
79 Ok(Self {
80 source,
81 destination,
82 apci,
83 payload: payload.to_vec(),
84 })
85 }
86
87 pub const fn source(&self) -> IndividualAddress {
88 self.source
89 }
90
91 pub const fn destination(&self) -> GroupAddress {
92 self.destination
93 }
94
95 pub const fn apci(&self) -> Apci {
96 self.apci
97 }
98
99 pub fn payload(&self) -> &[u8] {
100 &self.payload
101 }
102
103 fn encode_apdu(&self, out: &mut std::vec::Vec<u8>) -> Result<()> {
104 out.push(0x00);
105
106 match self.payload.as_slice() {
107 [] => out.push(self.apci.service_bits()),
108 [short] if *short <= 0x3f => out.push(self.apci.service_bits() | short),
109 payload => {
110 out.push(self.apci.service_bits());
111 out.extend_from_slice(payload);
112 }
113 }
114
115 Ok(())
116 }
117
118 fn decode_apdu(
119 source: IndividualAddress,
120 destination: GroupAddress,
121 apdu: &[u8],
122 ) -> Result<Self> {
123 if apdu.len() < 2 {
124 return Err(KnxError::BufferTooShort {
125 needed: 2,
126 actual: apdu.len(),
127 });
128 }
129 if apdu[0] != 0x00 {
130 return Err(KnxError::InvalidFrame("unsupported TPCI field"));
131 }
132
133 let apci = Apci::try_from(apdu[1])?;
134 let payload = if apdu.len() == 2 {
135 match apci {
136 Apci::GroupValueRead => std::vec::Vec::new(),
137 Apci::GroupValueResponse | Apci::GroupValueWrite => {
138 std::vec::Vec::from([apdu[1] & 0x3f])
139 }
140 }
141 } else {
142 if apdu[1] & 0x3f != 0 {
143 return Err(KnxError::InvalidFrame("extended APDU has inline data bits"));
144 }
145 apdu[2..].to_vec()
146 };
147
148 Self::new(source, destination, apci, &payload)
149 }
150}
151
152#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct CemiFrame {
155 message_code: CemiMessageCode,
156 control1: u8,
157 control2: u8,
158 telegram: GroupTelegram,
159 additional_info: std::vec::Vec<u8>,
163}
164
165impl CemiFrame {
166 pub const DEFAULT_CONTROL1: u8 = 0xbc;
167 pub const DEFAULT_CONTROL2_GROUP: u8 = 0xe0;
168
169 pub const fn new(message_code: CemiMessageCode, telegram: GroupTelegram) -> Self {
170 Self {
171 message_code,
172 control1: Self::DEFAULT_CONTROL1,
173 control2: Self::DEFAULT_CONTROL2_GROUP,
174 telegram,
175 additional_info: std::vec::Vec::new(),
176 }
177 }
178
179 pub fn group_value_read(source: IndividualAddress, destination: GroupAddress) -> Result<Self> {
180 Ok(Self::new(
181 CemiMessageCode::LDataRequest,
182 GroupTelegram::new(source, destination, Apci::GroupValueRead, &[])?,
183 ))
184 }
185
186 pub fn group_value_response(
187 source: IndividualAddress,
188 destination: GroupAddress,
189 payload: &[u8],
190 ) -> Result<Self> {
191 Ok(Self::new(
192 CemiMessageCode::LDataRequest,
193 GroupTelegram::new(source, destination, Apci::GroupValueResponse, payload)?,
194 ))
195 }
196
197 pub fn group_value_write(
198 source: IndividualAddress,
199 destination: GroupAddress,
200 payload: &[u8],
201 ) -> Result<Self> {
202 Ok(Self::new(
203 CemiMessageCode::LDataRequest,
204 GroupTelegram::new(source, destination, Apci::GroupValueWrite, payload)?,
205 ))
206 }
207
208 pub const fn message_code(&self) -> CemiMessageCode {
209 self.message_code
210 }
211
212 pub const fn control1(&self) -> u8 {
213 self.control1
214 }
215
216 pub const fn control2(&self) -> u8 {
217 self.control2
218 }
219
220 pub const fn telegram(&self) -> &GroupTelegram {
221 &self.telegram
222 }
223
224 pub fn additional_info(&self) -> &[u8] {
226 &self.additional_info
227 }
228
229 pub fn with_additional_info(
235 mut self,
236 additional_info: impl Into<std::vec::Vec<u8>>,
237 ) -> Result<Self> {
238 let additional_info = additional_info.into();
239 if u8::try_from(additional_info.len()).is_err() {
240 return Err(KnxError::InvalidFrame("additional info too long"));
241 }
242 self.additional_info = additional_info;
243 Ok(self)
244 }
245
246 pub fn decode(input: &[u8]) -> Result<(Self, &[u8])> {
247 if input.len() < 2 {
248 return Err(KnxError::BufferTooShort {
249 needed: 2,
250 actual: input.len(),
251 });
252 }
253
254 let message_code = CemiMessageCode::try_from(input[0])?;
255 let additional_info_len = input[1] as usize;
256 let fixed_start = 2 + additional_info_len;
257 let fixed_len = fixed_start + 7;
258
259 if input.len() < fixed_len {
260 return Err(KnxError::BufferTooShort {
261 needed: fixed_len,
262 actual: input.len(),
263 });
264 }
265
266 let additional_info = input[2..fixed_start].to_vec();
268 let control1 = input[fixed_start];
269 let control2 = input[fixed_start + 1];
270 let source = IndividualAddress::from_raw(u16::from_be_bytes([
271 input[fixed_start + 2],
272 input[fixed_start + 3],
273 ]));
274 let destination = GroupAddress::from_raw(u16::from_be_bytes([
275 input[fixed_start + 4],
276 input[fixed_start + 5],
277 ]));
278 let apdu_len = input[fixed_start + 6] as usize + 1;
279 let apdu_start = fixed_start + 7;
280 let needed = apdu_start + apdu_len;
281
282 if input.len() < needed {
283 return Err(KnxError::BufferTooShort {
284 needed,
285 actual: input.len(),
286 });
287 }
288
289 let telegram = GroupTelegram::decode_apdu(source, destination, &input[apdu_start..needed])?;
290
291 Ok((
292 Self {
293 message_code,
294 control1,
295 control2,
296 telegram,
297 additional_info,
298 },
299 &input[needed..],
300 ))
301 }
302
303 pub fn encode(&self, out: &mut std::vec::Vec<u8>) -> Result<()> {
304 let mut apdu = std::vec::Vec::new();
305 self.telegram.encode_apdu(&mut apdu)?;
306 let npdu_len = apdu
307 .len()
308 .checked_sub(1)
309 .and_then(|value| u8::try_from(value).ok())
310 .ok_or(KnxError::InvalidFrame("APDU length out of range"))?;
311 let additional_info_len = u8::try_from(self.additional_info.len())
312 .map_err(|_| KnxError::InvalidFrame("additional info too long"))?;
313
314 out.push(self.message_code.as_u8());
315 out.push(additional_info_len);
316 out.extend_from_slice(&self.additional_info);
317 out.extend_from_slice(&[
318 self.control1,
319 self.control2,
320 (self.telegram.source().raw() >> 8) as u8,
321 self.telegram.source().raw() as u8,
322 (self.telegram.destination().raw() >> 8) as u8,
323 self.telegram.destination().raw() as u8,
324 npdu_len,
325 ]);
326 out.extend_from_slice(&apdu);
327
328 Ok(())
329 }
330}