Skip to main content

mabi_knx/
cemi.rs

1//! Common EMI (cEMI) frame handling.
2//!
3//! cEMI is the standardized interface for KNX communication,
4//! used in KNXnet/IP tunneling and routing.
5
6use bytes::{Buf, BufMut, BytesMut};
7
8use crate::address::{AddressType, GroupAddress, IndividualAddress};
9use crate::error::{KnxError, KnxResult};
10
11// ============================================================================
12// Message Code
13// ============================================================================
14
15/// cEMI message code.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17#[repr(u8)]
18pub enum MessageCode {
19    /// L_Data.req - Request to send data.
20    LDataReq = 0x11,
21    /// L_Data.con - Confirmation of data sent.
22    LDataCon = 0x2E,
23    /// L_Data.ind - Indication of received data.
24    LDataInd = 0x29,
25    /// L_Busmon.ind - Bus monitor indication.
26    LBusmonInd = 0x2B,
27    /// L_Raw.req - Raw request.
28    LRawReq = 0x10,
29    /// L_Raw.ind - Raw indication.
30    LRawInd = 0x2D,
31    /// L_Raw.con - Raw confirmation.
32    LRawCon = 0x2F,
33    /// M_PropRead.req - Property read request.
34    MPropReadReq = 0xFC,
35    /// M_PropRead.con - Property read confirmation.
36    MPropReadCon = 0xFB,
37    /// M_PropWrite.req - Property write request.
38    MPropWriteReq = 0xF6,
39    /// M_PropWrite.con - Property write confirmation.
40    MPropWriteCon = 0xF5,
41    /// M_Reset.req - Reset request.
42    MResetReq = 0xF1,
43    /// M_Reset.ind - Reset indication.
44    MResetInd = 0xF0,
45}
46
47impl MessageCode {
48    /// Try to create from raw byte.
49    pub fn try_from_u8(value: u8) -> Option<Self> {
50        match value {
51            0x11 => Some(Self::LDataReq),
52            0x2E => Some(Self::LDataCon),
53            0x29 => Some(Self::LDataInd),
54            0x2B => Some(Self::LBusmonInd),
55            0x10 => Some(Self::LRawReq),
56            0x2D => Some(Self::LRawInd),
57            0x2F => Some(Self::LRawCon),
58            0xFC => Some(Self::MPropReadReq),
59            0xFB => Some(Self::MPropReadCon),
60            0xF6 => Some(Self::MPropWriteReq),
61            0xF5 => Some(Self::MPropWriteCon),
62            0xF1 => Some(Self::MResetReq),
63            0xF0 => Some(Self::MResetInd),
64            _ => None,
65        }
66    }
67
68    /// Check if this is a data message.
69    pub fn is_data(&self) -> bool {
70        matches!(self, Self::LDataReq | Self::LDataCon | Self::LDataInd)
71    }
72
73    /// Check if this is a request.
74    pub fn is_request(&self) -> bool {
75        matches!(
76            self,
77            Self::LDataReq | Self::LRawReq | Self::MPropReadReq | Self::MPropWriteReq | Self::MResetReq
78        )
79    }
80
81    /// Check if this is an indication.
82    pub fn is_indication(&self) -> bool {
83        matches!(
84            self,
85            Self::LDataInd | Self::LBusmonInd | Self::LRawInd | Self::MResetInd
86        )
87    }
88
89    /// Check if this is a confirmation.
90    pub fn is_confirmation(&self) -> bool {
91        matches!(
92            self,
93            Self::LDataCon | Self::LRawCon | Self::MPropReadCon | Self::MPropWriteCon
94        )
95    }
96
97    /// Check if this is a property service message.
98    pub fn is_property_service(&self) -> bool {
99        matches!(
100            self,
101            Self::MPropReadReq | Self::MPropReadCon | Self::MPropWriteReq | Self::MPropWriteCon
102        )
103    }
104
105    /// Check if this is a reset service message.
106    pub fn is_reset_service(&self) -> bool {
107        matches!(self, Self::MResetReq | Self::MResetInd)
108    }
109
110    /// Get the corresponding confirmation message code for a request.
111    pub fn to_confirmation(&self) -> Option<Self> {
112        match self {
113            Self::LDataReq => Some(Self::LDataCon),
114            Self::LRawReq => Some(Self::LRawCon),
115            Self::MPropReadReq => Some(Self::MPropReadCon),
116            Self::MPropWriteReq => Some(Self::MPropWriteCon),
117            Self::MResetReq => Some(Self::MResetInd),
118            _ => None,
119        }
120    }
121}
122
123impl From<MessageCode> for u8 {
124    fn from(code: MessageCode) -> Self {
125        code as u8
126    }
127}
128
129// ============================================================================
130// Priority
131// ============================================================================
132
133/// KNX telegram priority.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
135#[repr(u8)]
136pub enum Priority {
137    /// System priority (highest).
138    System = 0,
139    /// Normal priority.
140    #[default]
141    Normal = 1,
142    /// Urgent priority.
143    Urgent = 2,
144    /// Low priority (lowest).
145    Low = 3,
146}
147
148impl Priority {
149    /// Create from raw value (2 bits).
150    pub fn from_bits(value: u8) -> Self {
151        match value & 0x03 {
152            0 => Self::System,
153            1 => Self::Normal,
154            2 => Self::Urgent,
155            _ => Self::Low,
156        }
157    }
158
159    /// Convert to raw bits.
160    pub fn to_bits(self) -> u8 {
161        self as u8
162    }
163}
164
165// ============================================================================
166// APCI (Application Layer Protocol Control Information)
167// ============================================================================
168
169/// APCI - Application Protocol Control Information.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum Apci {
172    /// Group Value Read.
173    GroupValueRead,
174    /// Group Value Response.
175    GroupValueResponse,
176    /// Group Value Write.
177    GroupValueWrite,
178    /// Individual Address Write.
179    IndividualAddressWrite,
180    /// Individual Address Read.
181    IndividualAddressRead,
182    /// Individual Address Response.
183    IndividualAddressResponse,
184    /// ADC Read.
185    AdcRead,
186    /// ADC Response.
187    AdcResponse,
188    /// Memory Read.
189    MemoryRead,
190    /// Memory Response.
191    MemoryResponse,
192    /// Memory Write.
193    MemoryWrite,
194    /// User Message.
195    UserMessage,
196    /// Device Descriptor Read.
197    DeviceDescriptorRead,
198    /// Device Descriptor Response.
199    DeviceDescriptorResponse,
200    /// Restart.
201    Restart,
202    /// Escape (for extended APCI).
203    Escape,
204    /// Unknown APCI.
205    Unknown(u16),
206}
207
208impl Apci {
209    /// Decode APCI from raw 10-bit value.
210    pub fn decode(value: u16) -> Self {
211        // APCI encoding uses bits for different services
212        // Group Value services are in range 0x0000-0x00FF
213        // Check specific patterns
214
215        // Mask to the 10-bit APCI
216        let apci = value & 0x03FF;
217
218        match apci {
219            0x0000 => Self::GroupValueRead,
220            0x0040..=0x007F => Self::GroupValueResponse,
221            0x0080..=0x00FF => Self::GroupValueWrite,
222            0x0100..=0x013F => Self::IndividualAddressWrite,
223            0x0140..=0x017F => Self::IndividualAddressRead,
224            0x0180..=0x01BF => Self::IndividualAddressResponse,
225            0x01C0..=0x01FF => Self::AdcRead,
226            0x0200..=0x023F => Self::AdcResponse,
227            0x0240..=0x027F => Self::MemoryRead,
228            0x0280..=0x02BF => Self::MemoryResponse,
229            0x02C0..=0x02FF => Self::MemoryWrite,
230            0x0300..=0x033F => Self::UserMessage,
231            0x0340..=0x037F => Self::DeviceDescriptorRead,
232            0x0380..=0x03BF => Self::DeviceDescriptorResponse,
233            0x03C0..=0x03DF => Self::Restart,
234            0x03E0..=0x03FF => Self::Escape,
235            _ => Self::Unknown(value),
236        }
237    }
238
239    /// Decode APCI simplified - just match the base values.
240    pub fn decode_simple(value: u16) -> Self {
241        // Use only upper bits to determine main type
242        let base = value & 0x03C0; // Bits 9-6
243
244        match base {
245            0x0000 => Self::GroupValueRead,
246            0x0040 => Self::GroupValueResponse,
247            0x0080 => Self::GroupValueWrite,
248            0x00C0 => Self::GroupValueWrite, // Continuation
249            0x0100 => Self::IndividualAddressWrite,
250            0x0140 => Self::IndividualAddressRead,
251            0x0180 => Self::IndividualAddressResponse,
252            0x01C0 => Self::AdcRead,
253            0x0200 => Self::AdcResponse,
254            0x0240 => Self::MemoryRead,
255            0x0280 => Self::MemoryResponse,
256            0x02C0 => Self::MemoryWrite,
257            0x0300 => Self::UserMessage,
258            0x0340 => Self::DeviceDescriptorRead,
259            0x0380 => Self::DeviceDescriptorResponse,
260            0x03C0 => Self::Restart,
261            _ => Self::Unknown(value),
262        }
263    }
264
265    /// Encode APCI to raw 10-bit value.
266    pub fn encode(&self) -> u16 {
267        match self {
268            Self::GroupValueRead => 0x0000,
269            Self::GroupValueResponse => 0x0040,
270            Self::GroupValueWrite => 0x0080,
271            Self::IndividualAddressWrite => 0x0100,
272            Self::IndividualAddressRead => 0x0100,
273            Self::IndividualAddressResponse => 0x0140,
274            Self::AdcRead => 0x0180,
275            Self::AdcResponse => 0x01C0,
276            Self::MemoryRead => 0x0200,
277            Self::MemoryResponse => 0x0240,
278            Self::MemoryWrite => 0x0280,
279            Self::UserMessage => 0x02C0,
280            Self::DeviceDescriptorRead => 0x0300,
281            Self::DeviceDescriptorResponse => 0x0340,
282            Self::Restart => 0x0380,
283            Self::Escape => 0x03C0,
284            Self::Unknown(v) => *v,
285        }
286    }
287
288    /// Check if this is a group value service.
289    pub fn is_group_value(&self) -> bool {
290        matches!(
291            self,
292            Self::GroupValueRead | Self::GroupValueResponse | Self::GroupValueWrite
293        )
294    }
295
296    /// Check if this is a read request.
297    pub fn is_read(&self) -> bool {
298        matches!(
299            self,
300            Self::GroupValueRead
301                | Self::IndividualAddressRead
302                | Self::AdcRead
303                | Self::MemoryRead
304                | Self::DeviceDescriptorRead
305        )
306    }
307
308    /// Check if this is a write.
309    pub fn is_write(&self) -> bool {
310        matches!(
311            self,
312            Self::GroupValueWrite | Self::IndividualAddressWrite | Self::MemoryWrite
313        )
314    }
315
316    /// Check if this is a response.
317    pub fn is_response(&self) -> bool {
318        matches!(
319            self,
320            Self::GroupValueResponse
321                | Self::IndividualAddressResponse
322                | Self::AdcResponse
323                | Self::MemoryResponse
324                | Self::DeviceDescriptorResponse
325        )
326    }
327}
328
329// ============================================================================
330// Additional Info
331// ============================================================================
332
333/// cEMI additional information type.
334#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335#[repr(u8)]
336pub enum AdditionalInfoType {
337    /// PL Medium Information.
338    PlMediumInfo = 0x01,
339    /// RF Medium Information.
340    RfMediumInfo = 0x02,
341    /// Bus Monitor Info.
342    BusMonitorInfo = 0x03,
343    /// Timestamp Relative.
344    TimestampRelative = 0x04,
345    /// Time Delay Until.
346    TimeDelayUntil = 0x05,
347    /// Extended Relative Timestamp.
348    ExtendedTimestamp = 0x06,
349    /// BiBat Information.
350    BiBatInfo = 0x07,
351    /// RF Multi Information.
352    RfMultiInfo = 0x08,
353    /// Preamble And Postamble.
354    PreamblePostamble = 0x09,
355    /// RF Fast Ack Information.
356    RfFastAckInfo = 0x0A,
357    /// Manufacturer Specific Data.
358    ManufacturerData = 0xFE,
359}
360
361/// cEMI additional information.
362#[derive(Debug, Clone)]
363pub struct AdditionalInfo {
364    pub info_type: u8,
365    pub data: Vec<u8>,
366}
367
368impl AdditionalInfo {
369    /// Create new additional info.
370    pub fn new(info_type: u8, data: Vec<u8>) -> Self {
371        Self { info_type, data }
372    }
373
374    /// Encode to bytes.
375    pub fn encode(&self, buf: &mut BytesMut) {
376        buf.put_u8(self.info_type);
377        buf.put_u8(self.data.len() as u8);
378        buf.put_slice(&self.data);
379    }
380
381    /// Encoded length.
382    pub fn encoded_len(&self) -> usize {
383        2 + self.data.len()
384    }
385
386    /// Decode from buffer.
387    pub fn decode(buf: &mut &[u8]) -> KnxResult<Self> {
388        if buf.len() < 2 {
389            return Err(KnxError::frame_too_short(2, buf.len()));
390        }
391
392        let info_type = buf.get_u8();
393        let length = buf.get_u8() as usize;
394
395        if buf.len() < length {
396            return Err(KnxError::frame_too_short(length, buf.len()));
397        }
398
399        let data = buf[..length].to_vec();
400        *buf = &buf[length..];
401
402        Ok(Self { info_type, data })
403    }
404}
405
406// ============================================================================
407// cEMI Frame
408// ============================================================================
409
410/// cEMI L_Data frame.
411#[derive(Debug, Clone)]
412pub struct CemiFrame {
413    /// Message code.
414    pub message_code: MessageCode,
415    /// Additional information.
416    pub additional_info: Vec<AdditionalInfo>,
417    /// Source address.
418    pub source: IndividualAddress,
419    /// Destination address (raw).
420    pub destination: u16,
421    /// Address type.
422    pub address_type: AddressType,
423    /// Hop count (routing counter).
424    pub hop_count: u8,
425    /// Priority.
426    pub priority: Priority,
427    /// Confirm flag (for L_Data.con).
428    pub confirm: bool,
429    /// Acknowledge request.
430    pub ack_request: bool,
431    /// System broadcast.
432    pub system_broadcast: bool,
433    /// APCI.
434    pub apci: Apci,
435    /// Data (payload after APCI).
436    pub data: Vec<u8>,
437}
438
439impl CemiFrame {
440    /// Create a Group Value Write frame.
441    pub fn group_value_write(
442        source: IndividualAddress,
443        destination: GroupAddress,
444        data: Vec<u8>,
445    ) -> Self {
446        Self {
447            message_code: MessageCode::LDataInd,
448            additional_info: Vec::new(),
449            source,
450            destination: destination.raw(),
451            address_type: AddressType::Group,
452            hop_count: 6,
453            priority: Priority::Low,
454            confirm: false,
455            ack_request: false,
456            system_broadcast: false,
457            apci: Apci::GroupValueWrite,
458            data,
459        }
460    }
461
462    /// Create a Group Value Read frame.
463    pub fn group_value_read(source: IndividualAddress, destination: GroupAddress) -> Self {
464        Self {
465            message_code: MessageCode::LDataInd,
466            additional_info: Vec::new(),
467            source,
468            destination: destination.raw(),
469            address_type: AddressType::Group,
470            hop_count: 6,
471            priority: Priority::Low,
472            confirm: false,
473            ack_request: false,
474            system_broadcast: false,
475            apci: Apci::GroupValueRead,
476            data: Vec::new(),
477        }
478    }
479
480    /// Create a Group Value Response frame.
481    pub fn group_value_response(
482        source: IndividualAddress,
483        destination: GroupAddress,
484        data: Vec<u8>,
485    ) -> Self {
486        Self {
487            message_code: MessageCode::LDataInd,
488            additional_info: Vec::new(),
489            source,
490            destination: destination.raw(),
491            address_type: AddressType::Group,
492            hop_count: 6,
493            priority: Priority::Low,
494            confirm: false,
495            ack_request: false,
496            system_broadcast: false,
497            apci: Apci::GroupValueResponse,
498            data,
499        }
500    }
501
502    /// Create a Bus Monitor indication frame (MC=0x2B).
503    ///
504    /// Bus monitor frames wrap the raw bus frame data with Additional Info TLV:
505    /// - 0x03: Bus Monitor Status (error flags)
506    /// - 0x04: Timestamp Relative (2 bytes, ms since last frame)
507    /// - 0x06: Extended Timestamp (4 bytes, microseconds)
508    pub fn bus_monitor_indication(
509        raw_frame: &[u8],
510        status: u8,
511        timestamp_ms: u16,
512    ) -> Self {
513        let mut additional_info = Vec::new();
514
515        // Bus Monitor Info (type 0x03): 1 byte status
516        additional_info.push(AdditionalInfo::new(
517            AdditionalInfoType::BusMonitorInfo as u8,
518            vec![status],
519        ));
520
521        // Timestamp Relative (type 0x04): 2 bytes, milliseconds since last frame
522        additional_info.push(AdditionalInfo::new(
523            AdditionalInfoType::TimestampRelative as u8,
524            timestamp_ms.to_be_bytes().to_vec(),
525        ));
526
527        Self {
528            message_code: MessageCode::LBusmonInd,
529            additional_info,
530            source: IndividualAddress::new(0, 0, 0),
531            destination: 0,
532            address_type: AddressType::Group,
533            hop_count: 7,
534            priority: Priority::Low,
535            confirm: false,
536            ack_request: false,
537            system_broadcast: false,
538            apci: Apci::Unknown(0),
539            data: raw_frame.to_vec(),
540        }
541    }
542
543    /// Create a Bus Monitor indication with extended timestamp (microseconds).
544    pub fn bus_monitor_indication_ext(
545        raw_frame: &[u8],
546        status: u8,
547        timestamp_ms: u16,
548        timestamp_us: u32,
549    ) -> Self {
550        let mut frame = Self::bus_monitor_indication(raw_frame, status, timestamp_ms);
551
552        // Extended Relative Timestamp (type 0x06): 4 bytes, microseconds
553        frame.additional_info.push(AdditionalInfo::new(
554            AdditionalInfoType::ExtendedTimestamp as u8,
555            timestamp_us.to_be_bytes().to_vec(),
556        ));
557
558        frame
559    }
560
561    /// Create an M_PropRead.con response frame.
562    ///
563    /// Responds to M_PropRead.req with the requested property value.
564    /// The data format follows KNX cEMI property service encoding:
565    /// [object_index(2) | property_id(2) | count_index(2) | data...]
566    pub fn prop_read_con(
567        object_index: u16,
568        property_id: u16,
569        count: u8,
570        start_index: u8,
571        value: Vec<u8>,
572    ) -> Self {
573        let mut data = Vec::with_capacity(6 + value.len());
574        data.extend_from_slice(&object_index.to_be_bytes());
575        data.extend_from_slice(&property_id.to_be_bytes());
576        // Number of elements (4 bits) | start index (12 bits)
577        let count_index = ((count as u16) << 12) | (start_index as u16 & 0x0FFF);
578        data.extend_from_slice(&count_index.to_be_bytes());
579        data.extend(value);
580
581        Self {
582            message_code: MessageCode::MPropReadCon,
583            additional_info: Vec::new(),
584            source: IndividualAddress::new(0, 0, 0),
585            destination: 0,
586            address_type: AddressType::Individual,
587            hop_count: 7,
588            priority: Priority::System,
589            confirm: false,
590            ack_request: false,
591            system_broadcast: false,
592            apci: Apci::Unknown(0),
593            data,
594        }
595    }
596
597    /// Create an M_PropWrite.con response frame.
598    pub fn prop_write_con(
599        object_index: u16,
600        property_id: u16,
601        count: u8,
602        start_index: u8,
603        success: bool,
604    ) -> Self {
605        let mut data = Vec::with_capacity(6);
606        data.extend_from_slice(&object_index.to_be_bytes());
607        data.extend_from_slice(&property_id.to_be_bytes());
608        // On error: count=0 signals failure per KNX spec
609        let actual_count = if success { count } else { 0 };
610        let count_index = ((actual_count as u16) << 12) | (start_index as u16 & 0x0FFF);
611        data.extend_from_slice(&count_index.to_be_bytes());
612
613        Self {
614            message_code: MessageCode::MPropWriteCon,
615            additional_info: Vec::new(),
616            source: IndividualAddress::new(0, 0, 0),
617            destination: 0,
618            address_type: AddressType::Individual,
619            hop_count: 7,
620            priority: Priority::System,
621            confirm: !success,
622            ack_request: false,
623            system_broadcast: false,
624            apci: Apci::Unknown(0),
625            data,
626        }
627    }
628
629    /// Create an M_Reset.ind frame (response to M_Reset.req).
630    pub fn reset_ind() -> Self {
631        Self {
632            message_code: MessageCode::MResetInd,
633            additional_info: Vec::new(),
634            source: IndividualAddress::new(0, 0, 0),
635            destination: 0,
636            address_type: AddressType::Individual,
637            hop_count: 7,
638            priority: Priority::System,
639            confirm: false,
640            ack_request: false,
641            system_broadcast: false,
642            apci: Apci::Unknown(0),
643            data: Vec::new(),
644        }
645    }
646
647    /// Parse property service request data.
648    ///
649    /// Returns (object_index, property_id, count, start_index, remaining_data).
650    pub fn parse_property_request(&self) -> Option<(u16, u16, u8, u8, &[u8])> {
651        if self.data.len() < 6 {
652            return None;
653        }
654        let object_index = u16::from_be_bytes([self.data[0], self.data[1]]);
655        let property_id = u16::from_be_bytes([self.data[2], self.data[3]]);
656        let count_index = u16::from_be_bytes([self.data[4], self.data[5]]);
657        let count = (count_index >> 12) as u8;
658        let start_index = (count_index & 0x0FFF) as u8;
659        let remaining = &self.data[6..];
660        Some((object_index, property_id, count, start_index, remaining))
661    }
662
663    /// Get destination as group address.
664    pub fn destination_group(&self) -> Option<GroupAddress> {
665        if self.address_type == AddressType::Group {
666            Some(GroupAddress::from_raw(self.destination))
667        } else {
668            None
669        }
670    }
671
672    /// Get destination as individual address.
673    pub fn destination_individual(&self) -> Option<IndividualAddress> {
674        if self.address_type == AddressType::Individual {
675            Some(IndividualAddress::decode(self.destination))
676        } else {
677            None
678        }
679    }
680
681    /// Set destination as group address.
682    pub fn with_destination_group(mut self, addr: GroupAddress) -> Self {
683        self.destination = addr.raw();
684        self.address_type = AddressType::Group;
685        self
686    }
687
688    /// Set destination as individual address.
689    pub fn with_destination_individual(mut self, addr: IndividualAddress) -> Self {
690        self.destination = addr.encode();
691        self.address_type = AddressType::Individual;
692        self
693    }
694
695    /// Set priority.
696    pub fn with_priority(mut self, priority: Priority) -> Self {
697        self.priority = priority;
698        self
699    }
700
701    /// Set hop count.
702    pub fn with_hop_count(mut self, hop_count: u8) -> Self {
703        self.hop_count = hop_count.min(7);
704        self
705    }
706
707    /// Encode to bytes.
708    pub fn encode(&self) -> Vec<u8> {
709        let mut buf = BytesMut::new();
710
711        // Message code
712        buf.put_u8(self.message_code.into());
713
714        // Additional info length
715        let add_info_len: usize = self.additional_info.iter().map(|i| i.encoded_len()).sum();
716        buf.put_u8(add_info_len as u8);
717
718        // Additional info
719        for info in &self.additional_info {
720            info.encode(&mut buf);
721        }
722
723        // Property service and reset frames use a different body format:
724        // MC | add_info_len | [add_info...] | data
725        // No Ctrl1/Ctrl2/addresses/NPDU — just raw property data.
726        if self.message_code.is_property_service() || self.message_code.is_reset_service() {
727            buf.put_slice(&self.data);
728            return buf.to_vec();
729        }
730
731        // Bus monitor frames: MC | add_info_len | [add_info...] | raw_frame_data
732        // The additional info carries status/timestamp, data is the raw bus frame.
733        if self.message_code == MessageCode::LBusmonInd {
734            buf.put_slice(&self.data);
735            return buf.to_vec();
736        }
737
738        // Standard L_Data frame encoding (L_Data.req/con/ind, L_Raw)
739        // Control byte 1
740        let ctrl1 = 0x80 // Standard frame
741            | (if self.ack_request { 0x00 } else { 0x20 }) // L/H (ack flag inverted)
742            | (if !self.confirm { 0x00 } else { 0x01 }) // Confirm
743            | ((self.priority.to_bits() & 0x03) << 2); // Priority
744        buf.put_u8(ctrl1);
745
746        // Control byte 2
747        let ctrl2 = (if self.address_type == AddressType::Group {
748            0x80
749        } else {
750            0x00
751        }) | (self.hop_count & 0x07);
752        buf.put_u8(ctrl2);
753
754        // Source address
755        buf.put_u16(self.source.encode());
756
757        // Destination address
758        buf.put_u16(self.destination);
759
760        // NPDU
761        let apci = self.apci.encode();
762
763        // APCI internal value maps directly to first NPDU wire byte
764        let apci_byte = apci as u8;
765
766        if self.data.is_empty() {
767            // No data - APCI only (e.g., GroupValueRead)
768            buf.put_u8(1); // npdu_len = 1
769            buf.put_u8(apci_byte);
770        } else if self.data.len() == 1 && self.data[0] <= 0x3F {
771            // Small data - embed in low 6 bits of APCI byte
772            buf.put_u8(1); // npdu_len = 1
773            buf.put_u8(apci_byte | (self.data[0] & 0x3F));
774        } else {
775            // Full data: npdu_len = 1 (APCI byte) + data.len()
776            buf.put_u8((self.data.len() + 1) as u8);
777            buf.put_u8(apci_byte);
778            buf.put_slice(&self.data);
779        }
780
781        buf.to_vec()
782    }
783
784    /// Decode from bytes.
785    pub fn decode(data: &[u8]) -> KnxResult<Self> {
786        if data.len() < 2 {
787            return Err(KnxError::frame_too_short(2, data.len()));
788        }
789
790        let mut buf = data;
791
792        // Message code
793        let message_code = MessageCode::try_from_u8(buf.get_u8())
794            .ok_or_else(|| KnxError::UnknownMessageCode(data[0]))?;
795
796        // Additional info
797        let add_info_len = buf.get_u8() as usize;
798        let mut additional_info = Vec::new();
799
800        if add_info_len > 0 {
801            if buf.len() < add_info_len {
802                return Err(KnxError::frame_too_short(add_info_len, buf.len()));
803            }
804            let mut add_info_buf = &buf[..add_info_len];
805            while !add_info_buf.is_empty() {
806                additional_info.push(AdditionalInfo::decode(&mut add_info_buf)?);
807            }
808            buf = &buf[add_info_len..];
809        }
810
811        // Property service frames: MC | add_info_len | [add_info...] | data
812        if message_code.is_property_service() || message_code.is_reset_service() {
813            return Ok(Self {
814                message_code,
815                additional_info,
816                source: IndividualAddress::new(0, 0, 0),
817                destination: 0,
818                address_type: AddressType::Individual,
819                hop_count: 7,
820                priority: Priority::System,
821                confirm: false,
822                ack_request: false,
823                system_broadcast: false,
824                apci: Apci::Unknown(0),
825                data: buf.to_vec(),
826            });
827        }
828
829        // Bus monitor frames: MC | add_info_len | [add_info...] | raw_frame_data
830        if message_code == MessageCode::LBusmonInd {
831            return Ok(Self {
832                message_code,
833                additional_info,
834                source: IndividualAddress::new(0, 0, 0),
835                destination: 0,
836                address_type: AddressType::Group,
837                hop_count: 7,
838                priority: Priority::Low,
839                confirm: false,
840                ack_request: false,
841                system_broadcast: false,
842                apci: Apci::Unknown(0),
843                data: buf.to_vec(),
844            });
845        }
846
847        // Standard L_Data frame decoding
848        if buf.len() < 7 {
849            return Err(KnxError::frame_too_short(7, buf.len()));
850        }
851
852        // Control bytes
853        let ctrl1 = buf.get_u8();
854        let ctrl2 = buf.get_u8();
855
856        let priority = Priority::from_bits((ctrl1 >> 2) & 0x03);
857        let ack_request = ctrl1 & 0x20 == 0;
858        let confirm = ctrl1 & 0x01 != 0;
859        let system_broadcast = ctrl1 & 0x10 != 0;
860
861        let address_type = AddressType::from_bit(ctrl2 & 0x80 != 0);
862        let hop_count = ctrl2 & 0x07;
863
864        // Addresses
865        let source = IndividualAddress::decode(buf.get_u16());
866        let destination = buf.get_u16();
867
868        // NPDU
869        // npdu_len counts bytes starting from the TPCI/APCI byte (inclusive)
870        let npdu_len = buf.get_u8() as usize;
871        if buf.len() < npdu_len {
872            return Err(KnxError::frame_too_short(npdu_len, buf.len()));
873        }
874
875        // First NPDU byte: [TPCI(2 bits) | APCI(6 bits)]
876        // For standard data frames (TPCI=00), APCI bits map to:
877        //   0x00 = GroupValueRead, 0x40 = GroupValueResponse, 0x80 = GroupValueWrite
878        // Convert wire byte to internal 10-bit APCI format by using byte value directly
879        let apci_byte1 = buf.get_u8();
880        let apci_raw = apci_byte1 as u16; // maps directly to internal APCI format
881        let apci = Apci::decode(apci_raw);
882
883        let frame_data = if npdu_len <= 1 {
884            // Small data embedded in low 6 bits of first byte
885            let small = apci_byte1 & 0x3F;
886            if small != 0 { vec![small] } else { Vec::new() }
887        } else {
888            // Remaining npdu_len-1 bytes are data
889            buf[..npdu_len - 1].to_vec()
890        };
891
892        Ok(Self {
893            message_code,
894            additional_info,
895            source,
896            destination,
897            address_type,
898            hop_count,
899            priority,
900            confirm,
901            ack_request,
902            system_broadcast,
903            apci,
904            data: frame_data,
905        })
906    }
907}
908
909#[cfg(test)]
910mod tests {
911    use super::*;
912
913    #[test]
914    fn test_message_code() {
915        assert_eq!(MessageCode::try_from_u8(0x11), Some(MessageCode::LDataReq));
916        assert_eq!(MessageCode::try_from_u8(0x29), Some(MessageCode::LDataInd));
917        assert!(MessageCode::LDataReq.is_data());
918        assert!(MessageCode::LDataReq.is_request());
919    }
920
921    #[test]
922    fn test_priority() {
923        assert_eq!(Priority::from_bits(0), Priority::System);
924        assert_eq!(Priority::from_bits(3), Priority::Low);
925        assert_eq!(Priority::Normal.to_bits(), 1);
926    }
927
928    #[test]
929    fn test_apci_encode_decode() {
930        let tests = [
931            Apci::GroupValueRead,
932            Apci::GroupValueResponse,
933            Apci::GroupValueWrite,
934        ];
935
936        for apci in tests {
937            let encoded = apci.encode();
938            let decoded = Apci::decode(encoded);
939            assert_eq!(apci, decoded, "Failed for {:?}", apci);
940        }
941    }
942
943    #[test]
944    fn test_cemi_frame_group_value_write() {
945        let frame = CemiFrame::group_value_write(
946            IndividualAddress::new(1, 2, 3),
947            GroupAddress::three_level(1, 2, 3),
948            vec![0x01],
949        );
950
951        assert!(matches!(frame.apci, Apci::GroupValueWrite));
952        assert_eq!(frame.source.to_string(), "1.2.3");
953        assert_eq!(frame.destination_group().unwrap().to_string(), "1/2/3");
954    }
955
956    #[test]
957    fn test_cemi_frame_encode_decode() {
958        let original = CemiFrame::group_value_write(
959            IndividualAddress::new(1, 2, 3),
960            GroupAddress::three_level(1, 2, 100),
961            vec![0x55],
962        );
963
964        let encoded = original.encode();
965        let decoded = CemiFrame::decode(&encoded).unwrap();
966
967        assert!(matches!(decoded.apci, Apci::GroupValueWrite));
968        assert_eq!(decoded.source.to_string(), "1.2.3");
969        assert_eq!(decoded.destination_group().unwrap().to_string(), "1/2/100");
970        assert_eq!(decoded.data, vec![0x55]);
971    }
972
973    #[test]
974    fn test_cemi_frame_group_value_read() {
975        let frame = CemiFrame::group_value_read(
976            IndividualAddress::new(1, 1, 1),
977            GroupAddress::three_level(1, 0, 1),
978        );
979
980        let encoded = frame.encode();
981        let decoded = CemiFrame::decode(&encoded).unwrap();
982
983        assert!(matches!(decoded.apci, Apci::GroupValueRead));
984        assert!(decoded.data.is_empty() || decoded.data == vec![0]);
985    }
986
987    #[test]
988    fn test_message_code_confirmation_mapping() {
989        assert_eq!(MessageCode::LDataReq.to_confirmation(), Some(MessageCode::LDataCon));
990        assert_eq!(MessageCode::LRawReq.to_confirmation(), Some(MessageCode::LRawCon));
991        assert_eq!(MessageCode::MPropReadReq.to_confirmation(), Some(MessageCode::MPropReadCon));
992        assert_eq!(MessageCode::MPropWriteReq.to_confirmation(), Some(MessageCode::MPropWriteCon));
993        assert_eq!(MessageCode::MResetReq.to_confirmation(), Some(MessageCode::MResetInd));
994        assert_eq!(MessageCode::LDataCon.to_confirmation(), None);
995        assert_eq!(MessageCode::LDataInd.to_confirmation(), None);
996    }
997
998    #[test]
999    fn test_message_code_categories() {
1000        assert!(MessageCode::LDataCon.is_confirmation());
1001        assert!(MessageCode::LRawCon.is_confirmation());
1002        assert!(MessageCode::MPropReadCon.is_confirmation());
1003        assert!(!MessageCode::LDataReq.is_confirmation());
1004
1005        assert!(MessageCode::MPropReadReq.is_property_service());
1006        assert!(MessageCode::MPropWriteReq.is_property_service());
1007        assert!(MessageCode::MPropReadCon.is_property_service());
1008        assert!(!MessageCode::LDataReq.is_property_service());
1009
1010        assert!(MessageCode::MResetReq.is_reset_service());
1011        assert!(MessageCode::MResetInd.is_reset_service());
1012        assert!(!MessageCode::LDataReq.is_reset_service());
1013    }
1014
1015    #[test]
1016    fn test_bus_monitor_indication() {
1017        let raw_frame = vec![0x29, 0x00, 0xBC, 0x11, 0x01, 0x09, 0x01, 0x00, 0x80, 0x01];
1018        let frame = CemiFrame::bus_monitor_indication(&raw_frame, 0x00, 150);
1019
1020        assert_eq!(frame.message_code, MessageCode::LBusmonInd);
1021        assert_eq!(frame.additional_info.len(), 2);
1022
1023        // Bus Monitor Info (type 0x03)
1024        assert_eq!(frame.additional_info[0].info_type, AdditionalInfoType::BusMonitorInfo as u8);
1025        assert_eq!(frame.additional_info[0].data, vec![0x00]);
1026
1027        // Timestamp Relative (type 0x04)
1028        assert_eq!(frame.additional_info[1].info_type, AdditionalInfoType::TimestampRelative as u8);
1029        assert_eq!(frame.additional_info[1].data, vec![0x00, 0x96]); // 150 in BE
1030
1031        // Raw frame data
1032        assert_eq!(frame.data, raw_frame);
1033
1034        // Verify encode/decode roundtrip
1035        let encoded = frame.encode();
1036        let decoded = CemiFrame::decode(&encoded).unwrap();
1037        assert_eq!(decoded.message_code, MessageCode::LBusmonInd);
1038        assert_eq!(decoded.additional_info.len(), 2);
1039        assert_eq!(decoded.data, raw_frame);
1040    }
1041
1042    #[test]
1043    fn test_bus_monitor_indication_ext() {
1044        let raw_frame = vec![0x29, 0x00, 0xBC];
1045        let frame = CemiFrame::bus_monitor_indication_ext(&raw_frame, 0x80, 100, 123456);
1046
1047        assert_eq!(frame.additional_info.len(), 3);
1048        // Extended Timestamp (type 0x06)
1049        assert_eq!(frame.additional_info[2].info_type, AdditionalInfoType::ExtendedTimestamp as u8);
1050        let ts_bytes = &frame.additional_info[2].data;
1051        let ts = u32::from_be_bytes([ts_bytes[0], ts_bytes[1], ts_bytes[2], ts_bytes[3]]);
1052        assert_eq!(ts, 123456);
1053    }
1054
1055    #[test]
1056    fn test_prop_read_con() {
1057        let frame = CemiFrame::prop_read_con(0, 11, 1, 1, vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
1058        assert_eq!(frame.message_code, MessageCode::MPropReadCon);
1059
1060        // Parse the property request back
1061        let parsed = frame.parse_property_request().unwrap();
1062        assert_eq!(parsed.0, 0);  // object_index
1063        assert_eq!(parsed.1, 11); // property_id (serial number)
1064        assert_eq!(parsed.2, 1);  // count
1065        assert_eq!(parsed.3, 1);  // start_index
1066        assert_eq!(parsed.4, &[0x00, 0x00, 0x00, 0x00, 0x00, 0x01]); // value
1067
1068        // Verify encode/decode roundtrip
1069        let encoded = frame.encode();
1070        let decoded = CemiFrame::decode(&encoded).unwrap();
1071        assert_eq!(decoded.message_code, MessageCode::MPropReadCon);
1072        assert_eq!(decoded.data, frame.data);
1073    }
1074
1075    #[test]
1076    fn test_prop_write_con_success() {
1077        let frame = CemiFrame::prop_write_con(0, 14, 1, 1, true);
1078        assert_eq!(frame.message_code, MessageCode::MPropWriteCon);
1079        assert!(!frame.confirm); // success: confirm=false
1080
1081        let parsed = frame.parse_property_request().unwrap();
1082        assert_eq!(parsed.2, 1); // count=1 on success
1083    }
1084
1085    #[test]
1086    fn test_prop_write_con_failure() {
1087        let frame = CemiFrame::prop_write_con(0, 14, 1, 1, false);
1088        assert!(frame.confirm); // failure: confirm=true
1089
1090        let parsed = frame.parse_property_request().unwrap();
1091        assert_eq!(parsed.2, 0); // count=0 signals error per KNX spec
1092    }
1093
1094    #[test]
1095    fn test_reset_ind() {
1096        let frame = CemiFrame::reset_ind();
1097        assert_eq!(frame.message_code, MessageCode::MResetInd);
1098        assert!(frame.data.is_empty());
1099
1100        // Verify encode/decode roundtrip
1101        let encoded = frame.encode();
1102        let decoded = CemiFrame::decode(&encoded).unwrap();
1103        assert_eq!(decoded.message_code, MessageCode::MResetInd);
1104    }
1105
1106    #[test]
1107    fn test_parse_property_request_too_short() {
1108        let frame = CemiFrame {
1109            message_code: MessageCode::MPropReadReq,
1110            additional_info: Vec::new(),
1111            source: IndividualAddress::new(0, 0, 0),
1112            destination: 0,
1113            address_type: AddressType::Individual,
1114            hop_count: 7,
1115            priority: Priority::System,
1116            confirm: false,
1117            ack_request: false,
1118            system_broadcast: false,
1119            apci: Apci::Unknown(0),
1120            data: vec![0x00, 0x00], // Only 2 bytes, need 6
1121        };
1122
1123        assert!(frame.parse_property_request().is_none());
1124    }
1125}