Skip to main content

knx_core/
knxnetip.rs

1use core::convert::TryFrom;
2
3use crate::{KnxError, Result};
4
5pub const HEADER_LENGTH: u8 = 0x06;
6pub const PROTOCOL_VERSION_1_0: u8 = 0x10;
7
8#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u16)]
11pub enum ServiceType {
12    SearchRequest = 0x0201,
13    SearchResponse = 0x0202,
14    DescriptionRequest = 0x0203,
15    DescriptionResponse = 0x0204,
16    ConnectRequest = 0x0205,
17    ConnectResponse = 0x0206,
18    ConnectionStateRequest = 0x0207,
19    ConnectionStateResponse = 0x0208,
20    DisconnectRequest = 0x0209,
21    DisconnectResponse = 0x020a,
22    TunnellingRequest = 0x0420,
23    TunnellingAck = 0x0421,
24    RoutingIndication = 0x0530,
25    RoutingLostMessage = 0x0531,
26    RoutingBusy = 0x0532,
27}
28
29impl ServiceType {
30    /// All `ServiceType` variants in declaration order; the canonical single
31    /// source for iteration/decoding.
32    pub const ALL: [ServiceType; 15] = [
33        ServiceType::SearchRequest,
34        ServiceType::SearchResponse,
35        ServiceType::DescriptionRequest,
36        ServiceType::DescriptionResponse,
37        ServiceType::ConnectRequest,
38        ServiceType::ConnectResponse,
39        ServiceType::ConnectionStateRequest,
40        ServiceType::ConnectionStateResponse,
41        ServiceType::DisconnectRequest,
42        ServiceType::DisconnectResponse,
43        ServiceType::TunnellingRequest,
44        ServiceType::TunnellingAck,
45        ServiceType::RoutingIndication,
46        ServiceType::RoutingLostMessage,
47        ServiceType::RoutingBusy,
48    ];
49
50    pub const fn as_u16(self) -> u16 {
51        self as u16
52    }
53}
54
55// `ServiceType::ALL` together with `as_u16` is now the single source of truth
56// for the variant <-> discriminant mapping; decoding scans `ALL` rather than
57// mirroring the discriminants in a separate table.
58impl TryFrom<u16> for ServiceType {
59    type Error = KnxError;
60
61    fn try_from(value: u16) -> Result<Self> {
62        ServiceType::ALL
63            .iter()
64            .copied()
65            .find(|st| st.as_u16() == value)
66            .ok_or(KnxError::UnsupportedServiceType(value))
67    }
68}
69
70#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72pub struct KnxNetIpHeader {
73    service_type: ServiceType,
74    total_length: u16,
75}
76
77impl KnxNetIpHeader {
78    pub const fn new(service_type: ServiceType, total_length: u16) -> Result<Self> {
79        if total_length < HEADER_LENGTH as u16 {
80            return Err(KnxError::InvalidFrame("total length shorter than header"));
81        }
82
83        Ok(Self {
84            service_type,
85            total_length,
86        })
87    }
88
89    pub const fn service_type(self) -> ServiceType {
90        self.service_type
91    }
92
93    pub const fn total_length(self) -> u16 {
94        self.total_length
95    }
96
97    pub fn decode(input: &[u8]) -> Result<(Self, &[u8])> {
98        if input.len() < HEADER_LENGTH as usize {
99            return Err(KnxError::BufferTooShort {
100                needed: HEADER_LENGTH as usize,
101                actual: input.len(),
102            });
103        }
104
105        if input[0] != HEADER_LENGTH {
106            return Err(KnxError::InvalidFrame("invalid KNXnet/IP header length"));
107        }
108        if input[1] != PROTOCOL_VERSION_1_0 {
109            return Err(KnxError::InvalidFrame("invalid KNXnet/IP protocol version"));
110        }
111
112        let service_type = ServiceType::try_from(u16::from_be_bytes([input[2], input[3]]))?;
113        let total_length = u16::from_be_bytes([input[4], input[5]]);
114        let header = Self::new(service_type, total_length)?;
115
116        let total_length = usize::from(total_length);
117        if input.len() < total_length {
118            return Err(KnxError::BufferTooShort {
119                needed: total_length,
120                actual: input.len(),
121            });
122        }
123
124        Ok((header, &input[HEADER_LENGTH as usize..total_length]))
125    }
126
127    #[cfg(feature = "std")]
128    pub fn encode(self, out: &mut std::vec::Vec<u8>) -> Result<()> {
129        out.extend_from_slice(&[
130            HEADER_LENGTH,
131            PROTOCOL_VERSION_1_0,
132            (self.service_type.as_u16() >> 8) as u8,
133            self.service_type.as_u16() as u8,
134            (self.total_length >> 8) as u8,
135            self.total_length as u8,
136        ]);
137        Ok(())
138    }
139}
140
141#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143#[repr(u8)]
144pub enum HostProtocol {
145    Ipv4Udp = 0x01,
146    Ipv4Tcp = 0x02,
147}
148
149// All `HostProtocol` variants in declaration order; together with `as_u8`
150// this is the single source of truth for the variant <-> byte mapping.
151const HOST_PROTOCOL_ALL: [HostProtocol; 2] = [HostProtocol::Ipv4Udp, HostProtocol::Ipv4Tcp];
152
153impl HostProtocol {
154    pub const fn as_u8(self) -> u8 {
155        self as u8
156    }
157}
158
159impl TryFrom<u8> for HostProtocol {
160    type Error = KnxError;
161
162    fn try_from(value: u8) -> Result<Self> {
163        HOST_PROTOCOL_ALL
164            .iter()
165            .copied()
166            .find(|hp| hp.as_u8() == value)
167            .ok_or(KnxError::InvalidFrame("unsupported host protocol"))
168    }
169}
170
171#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
173pub struct Hpai {
174    protocol: HostProtocol,
175    address: [u8; 4],
176    port: u16,
177}
178
179impl Hpai {
180    pub const LENGTH: u8 = 0x08;
181
182    pub const fn new(protocol: HostProtocol, address: [u8; 4], port: u16) -> Self {
183        Self {
184            protocol,
185            address,
186            port,
187        }
188    }
189
190    pub const fn protocol(self) -> HostProtocol {
191        self.protocol
192    }
193
194    pub const fn address(self) -> [u8; 4] {
195        self.address
196    }
197
198    pub const fn port(self) -> u16 {
199        self.port
200    }
201
202    pub fn decode(input: &[u8]) -> Result<(Self, &[u8])> {
203        if input.len() < Self::LENGTH as usize {
204            return Err(KnxError::BufferTooShort {
205                needed: Self::LENGTH as usize,
206                actual: input.len(),
207            });
208        }
209        if input[0] != Self::LENGTH {
210            return Err(KnxError::InvalidFrame("invalid HPAI length"));
211        }
212
213        let protocol = HostProtocol::try_from(input[1])?;
214        let address = [input[2], input[3], input[4], input[5]];
215        let port = u16::from_be_bytes([input[6], input[7]]);
216
217        Ok((
218            Self::new(protocol, address, port),
219            &input[Self::LENGTH as usize..],
220        ))
221    }
222
223    #[cfg(feature = "std")]
224    pub fn encode(self, out: &mut std::vec::Vec<u8>) -> Result<()> {
225        out.extend_from_slice(&[
226            Self::LENGTH,
227            self.protocol.as_u8(),
228            self.address[0],
229            self.address[1],
230            self.address[2],
231            self.address[3],
232            (self.port >> 8) as u8,
233            self.port as u8,
234        ]);
235        Ok(())
236    }
237}
238
239#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
241pub struct ConnectionHeader {
242    channel_id: u8,
243    sequence_counter: u8,
244    status: u8,
245}
246
247impl ConnectionHeader {
248    pub const LENGTH: u8 = 0x04;
249
250    pub const fn new(channel_id: u8, sequence_counter: u8, status: u8) -> Self {
251        Self {
252            channel_id,
253            sequence_counter,
254            status,
255        }
256    }
257
258    pub const fn channel_id(self) -> u8 {
259        self.channel_id
260    }
261
262    pub const fn sequence_counter(self) -> u8 {
263        self.sequence_counter
264    }
265
266    pub const fn status(self) -> u8 {
267        self.status
268    }
269
270    pub fn decode(input: &[u8]) -> Result<(Self, &[u8])> {
271        if input.len() < Self::LENGTH as usize {
272            return Err(KnxError::BufferTooShort {
273                needed: Self::LENGTH as usize,
274                actual: input.len(),
275            });
276        }
277        if input[0] != Self::LENGTH {
278            return Err(KnxError::InvalidFrame("invalid connection header length"));
279        }
280
281        Ok((
282            Self::new(input[1], input[2], input[3]),
283            &input[Self::LENGTH as usize..],
284        ))
285    }
286
287    #[cfg(feature = "std")]
288    pub fn encode(self, out: &mut std::vec::Vec<u8>) -> Result<()> {
289        out.extend_from_slice(&[
290            Self::LENGTH,
291            self.channel_id,
292            self.sequence_counter,
293            self.status,
294        ]);
295        Ok(())
296    }
297}