mod dib;
mod header;
mod hpai;
mod service;
pub use dib::{DeviceInfo as DibDeviceInfo, Dib, DibType, SupportedServiceFamilies};
pub use header::{KnxNetIpHeader, HEADER_SIZE, PROTOCOL_VERSION};
pub use hpai::{HostProtocol, Hpai};
pub use service::{ServiceFamily, ServiceType};
use crate::error::{KnxError, KnxResult};
use bytes::{Buf, BufMut, BytesMut};
#[derive(Debug, Clone)]
pub struct KnxFrame {
pub service_type: ServiceType,
pub body: Vec<u8>,
}
impl KnxFrame {
pub fn new(service_type: ServiceType, body: Vec<u8>) -> Self {
Self { service_type, body }
}
pub fn empty(service_type: ServiceType) -> Self {
Self {
service_type,
body: Vec::new(),
}
}
pub fn total_length(&self) -> usize {
HEADER_SIZE as usize + self.body.len()
}
pub fn encode(&self) -> Vec<u8> {
let total_length = self.total_length();
let mut buf = BytesMut::with_capacity(total_length);
buf.put_u8(HEADER_SIZE);
buf.put_u8(PROTOCOL_VERSION);
buf.put_u16(self.service_type.into());
buf.put_u16(total_length as u16);
buf.put_slice(&self.body);
buf.to_vec()
}
pub fn decode(data: &[u8]) -> KnxResult<Self> {
if data.len() < HEADER_SIZE as usize {
return Err(KnxError::frame_too_short(HEADER_SIZE as usize, data.len()));
}
let mut buf = data;
let header_size = buf.get_u8();
if header_size != HEADER_SIZE {
return Err(KnxError::InvalidHeader(format!(
"Expected header size {}, got {}",
HEADER_SIZE, header_size
)));
}
let version = buf.get_u8();
if version != PROTOCOL_VERSION {
return Err(KnxError::InvalidProtocolVersion {
expected: PROTOCOL_VERSION,
actual: version,
});
}
let service_type = ServiceType::try_from(buf.get_u16())?;
let total_length = buf.get_u16() as usize;
if data.len() < total_length {
return Err(KnxError::FrameLengthMismatch {
header_length: total_length,
actual_length: data.len(),
});
}
let body = buf[..(total_length - HEADER_SIZE as usize)].to_vec();
Ok(Self { service_type, body })
}
pub fn parse_header(data: &[u8]) -> KnxResult<KnxNetIpHeader> {
KnxNetIpHeader::decode(data)
}
pub fn frame_length(data: &[u8]) -> Option<usize> {
if data.len() < HEADER_SIZE as usize {
return None;
}
Some(u16::from_be_bytes([data[4], data[5]]) as usize)
}
}
pub struct FrameBuilder {
service_type: ServiceType,
body: BytesMut,
}
impl FrameBuilder {
pub fn new(service_type: ServiceType) -> Self {
Self {
service_type,
body: BytesMut::new(),
}
}
pub fn put_bytes(mut self, data: &[u8]) -> Self {
self.body.put_slice(data);
self
}
pub fn put_u8(mut self, value: u8) -> Self {
self.body.put_u8(value);
self
}
pub fn put_u16(mut self, value: u16) -> Self {
self.body.put_u16(value);
self
}
pub fn put_u32(mut self, value: u32) -> Self {
self.body.put_u32(value);
self
}
pub fn put_hpai(self, hpai: &Hpai) -> Self {
self.put_bytes(&hpai.encode())
}
pub fn build(self) -> KnxFrame {
KnxFrame {
service_type: self.service_type,
body: self.body.to_vec(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_encode_decode() {
let frame = KnxFrame::new(ServiceType::SearchRequest, vec![0x01, 0x02, 0x03]);
let encoded = frame.encode();
assert_eq!(encoded.len(), HEADER_SIZE as usize + 3);
let decoded = KnxFrame::decode(&encoded).unwrap();
assert_eq!(decoded.service_type, ServiceType::SearchRequest);
assert_eq!(decoded.body, vec![0x01, 0x02, 0x03]);
}
#[test]
fn test_frame_builder() {
let frame = FrameBuilder::new(ServiceType::ConnectRequest)
.put_u8(0x01)
.put_u16(0x0203)
.build();
assert_eq!(frame.service_type, ServiceType::ConnectRequest);
assert_eq!(frame.body, vec![0x01, 0x02, 0x03]);
}
#[test]
fn test_frame_length() {
let frame = KnxFrame::new(ServiceType::SearchRequest, vec![0x00; 10]);
let encoded = frame.encode();
assert_eq!(KnxFrame::frame_length(&encoded), Some(16)); }
}