Files
protocol_300/src/message.rs
2026-01-06 15:47:45 +01:00

519 lines
19 KiB
Rust

use log::{warn, error};
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use alloc::borrow::ToOwned;
use thiserror::Error;
use crate::{enums::{FunctionCode, MessageIdentifier}, utils::calculate_checksum};
#[derive(PartialEq, Eq, Debug)]
#[must_use]
pub struct Protocol300Message {
/// Length of the user data in the telegram
pub telegram_length: u8,
/// Message Identifier, Request, Response etc.
pub message_identifier: MessageIdentifier,
/// Function Code, Read Write or RPC
pub function_code: FunctionCode,
/// Data address, 2 bytes represented as u16
pub data_address: u16,
/// Expected return bytes (if it is a request) or
/// Length of the data to write (if it is a write)
pub data_length: u8,
/// Return value of the Vitocontrol if it's a read, if it's a write message
/// it contains the number ofexpected return bytes
pub data: Vec<u8>,
/// Checksum
pub checksum: u8,
}
impl Protocol300Message {
#[inline]
/// Creates a new `Protocol300message` used to request data from the device
///
/// # Arguments
/// * `data_address`: Address of the data to read
/// * `data_length`: Length of the data to be read.
pub fn new_request_message(data_address: u16, data_length: u8) -> Self{
let telegram_length: u8 = 5;
let message_identifier = MessageIdentifier::Request;
let function_code = FunctionCode::VirtualREAD;
let data: Vec<u8> = vec![];
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
Self {
telegram_length,
message_identifier,
function_code,
data_address,
data_length,
data: vec![],
checksum
}
}
#[inline]
/// Creates a new `Protocol300message` used to respond to a message requesting data
///
/// # Arguments
/// * `data_address`: Address of the data to read
/// * `data`: Data which should be used to respond to the message
///
/// # Panics
/// Panics if the data vec is longer than 256 (u8), can never happen in protocol 300
pub fn new_read_response_message(data_address: u16, data: &[u8]) -> Self{
let message_identifier = MessageIdentifier::Response;
let function_code = FunctionCode::VirtualREAD;
let data_length = u8::try_from(data.len()).expect("Data should never be longer than 256");
let telegram_length: u8 = 5 + data_length;
let data = data.to_owned();
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
Self {
telegram_length,
message_identifier,
function_code,
data_address,
data_length,
data,
checksum
}
}
#[inline]
/// Creates a new `Protocol300message` used to respond to a message writing data
///
/// # Arguments
/// * `data_address`: Address of the data to read
/// * `data`: Data which should be used to respond to the message
///
/// # Panics
/// Panics if the data vec is longer than 256 (u8), can never happen in protocol 300
pub fn new_write_response_message(data_address: u16, written_data_length: u8) -> Self{
let message_identifier = MessageIdentifier::Response;
let function_code = FunctionCode::VirtualWRITE;
let telegram_length: u8 = 5;
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, written_data_length, &[]);
Self {
telegram_length,
message_identifier,
function_code,
data_address,
data_length: written_data_length,
data: vec![],
checksum
}
}
#[inline]
/// Creates a new `Protocol300message` used to write data to the device
///
/// # Arguments
/// * `data_address`: Address of the data to read
/// * `data`: The data that should be written as vec of u8. Data length will be automatically infered from the length
/// of that vec
///
/// # Errors
/// Returns an error if the data vec is longer than u8. This is also against the spec of Protocol 300, so it's safe to panic
pub fn try_new_write_message(data_address: u16, data: &[u8]) -> Result<Self, MessageCreationError>{
let data_length: u8 = data.len().try_into()?;
let telegram_length: u8 = 5 + data_length;
let message_identifier = MessageIdentifier::Request;
let function_code = FunctionCode::VirtualWRITE;
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, data);
Ok(Self {
telegram_length,
message_identifier,
function_code,
data_address,
data_length,
data: data.to_vec(),
checksum
})
}
#[inline]
/// Decode a byte string into a `Protocol300Message`
///
/// Tries to decode a vec of bytes into an `Protocol300Message`. Includes
/// checking for valid start bytes and checking the checksum to detect
/// transmission errors
///
/// # Errors
/// Returns a `MessageDecodingError` on failure. Can be caused by multiple
/// things (for details look at `MessageDecodingError`):
/// - Invalid Start byte
/// - Length does not match actual length
/// - Invalid values for Message Identifier or Function code
/// - Checksum mismatch
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, MessageDecodingError> {
if bytes.len() < 8 {
return Err(MessageDecodingError::VecTooShort)
}
if bytes[0] != 0x41 {
return Err(MessageDecodingError::InvalidTelegramStart(bytes[0]));
}
let telegram_length: u8 = bytes[1];
if u8::try_from(bytes.len())? != telegram_length + 3 {
return Err(MessageDecodingError::LengthMismatch());
}
let message_identifier = MessageIdentifier::try_from(bytes[2])?;
let function_code = FunctionCode::try_from(bytes[3])?;
let data_address = (u16::from(bytes[4]) << 8) | u16::from(bytes[5]);
let data_length = bytes[6];
let data = match (message_identifier, function_code) {
// Read requests, write responses and CommError messages have no data content
(MessageIdentifier::Request, FunctionCode::VirtualREAD) | (MessageIdentifier::Response, FunctionCode::VirtualWRITE) | (MessageIdentifier::CommError, _) => vec![],
(MessageIdentifier::Request, FunctionCode::VirtualWRITE) | (MessageIdentifier::Response, FunctionCode::VirtualREAD) => {
let mut data: Vec<u8> = Vec::new();
for i in 0..data_length {
data.push(bytes[7 + i as usize]);
}
data
}
(_, FunctionCode::RemoteProcedureCall) => {
warn!("Decoding of RPCs is currently not implemented, do not expect correct data, mainly in the 'data' field");
vec![]
},
(MessageIdentifier::Unacked, _) => {
warn!("No idea what 'unacked' messages are, these will not decode correctly");
vec![]
},
};
let checksum_calculated = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
let checksum = bytes[telegram_length as usize + 2];
// Fail if the checksums do not match
if checksum != checksum_calculated {
return Err(MessageDecodingError::Checksum)
}
Ok(Self {
telegram_length,
message_identifier,
function_code,
data_address,
data_length,
data,
checksum
})
}
#[must_use]
#[inline]
/// Converts a `Protocol300Message` to a vec of bytes
pub fn to_bytes(&self) -> Vec<u8> {
let telegram_start_byte = 0x41;
let address_as_bytes = self.data_address.to_be_bytes();
let checksum = calculate_checksum(self.telegram_length, self.message_identifier, self.function_code, self.data_address, self.data_length, &self.data);
let mut bytes_vec = vec![
telegram_start_byte,
self.telegram_length,
self.message_identifier.into(),
self.function_code.into(),
address_as_bytes[0],
address_as_bytes[1],
self.data_length,
];
bytes_vec.extend_from_slice(&self.data);
bytes_vec.push(checksum);
bytes_vec
}
#[inline]
/// Returns the expected length of the return telegram, include start byte but excluding ACK byte
///
/// Currently only implemented for Read and Write requests, as the rest is not needed and/or
/// unknown behaviour
///
/// # Errors
/// - `MessageReturnLengthError` - Describes the error which occured when converting
pub const fn get_expected_return_telegram_length(&self) -> Result<usize, MessageReturnLengthError> {
match (self.message_identifier, self.function_code) {
(MessageIdentifier::Request, FunctionCode::VirtualREAD) => Ok(8 + self.data_length as usize),
(MessageIdentifier::Request, FunctionCode::VirtualWRITE) => Ok(8),
(MessageIdentifier::Response, _) => Err(MessageReturnLengthError::NonsensicalRequest),
_ => Err(MessageReturnLengthError::Unimplemented())
}
}
}
/// Errors that can occur while creating a message
#[derive(Error, Debug, PartialEq, Eq)]
pub enum MessageCreationError {
/// The data length is longer than u8
#[error("Data too long")]
DataTooLong(#[from] core::num::TryFromIntError),
}
/// Errors that can occur while getting the expected return length of a message
#[derive(Error, Debug, PartialEq, Eq)]
pub enum MessageReturnLengthError {
/// The data length is longer than u8
#[error("Unimplemented")]
Unimplemented(),
/// It makes no sense to get the expected length for a response
#[error("Nonsensical request")]
NonsensicalRequest
}
/// Errors that can occur while decoding a message from bytes
#[derive(Error, Debug, PartialEq, Eq)]
pub enum MessageDecodingError {
/// The data length is longer than u8
#[error("Start of the telegram is not 0x41 but {0:02X?}")]
InvalidTelegramStart(u8),
/// Error getiting the length of the bytes slice, should never happen
#[error("Error getting the length of the bytes slice")]
NoLength(#[from] core::num::TryFromIntError),
/// The advertiesed telegram length and actual length do not match
#[error("The advertiesed telegram length and actual length do not match")]
LengthMismatch(),
/// Error getting message identifier enum variant by number
#[error("Message Identifier not valid")]
InvalidMessageIdentifier(#[from] num_enum::TryFromPrimitiveError<MessageIdentifier>),
/// Error getting function code enum variant by number
#[error("Function Code not valid")]
InvalidFunctionCode(#[from] num_enum::TryFromPrimitiveError<FunctionCode>),
/// Checksum does not match the calculated checksum
#[error("Checksums do not match")]
Checksum,
/// The message vec is too short
#[error("The message vec is too short")]
VecTooShort
}
#[cfg(test)]
mod tests {
use crate::{enums::{FunctionCode, MessageIdentifier}, message::{MessageCreationError, Protocol300Message}};
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[test]
fn new_request_message_works() {
let expected_message = Protocol300Message{
telegram_length: 5,
message_identifier: MessageIdentifier::Request,
function_code: FunctionCode::VirtualREAD,
data_address: 0x0101,
data_length: 2,
data: vec![],
checksum: 0x0A
};
let generated_message = Protocol300Message::new_request_message(0x0101, 2);
assert_eq!(expected_message, generated_message);
}
#[test]
fn new_write_message() {
let expected_message = Protocol300Message{
telegram_length: 9,
message_identifier: MessageIdentifier::Request,
function_code: FunctionCode::VirtualWRITE,
data_address: 0x0101,
data_length: 4,
data: vec![0x01, 0x02, 0x03, 0x04],
checksum: 0x1B
};
let generated_message = Protocol300Message::try_new_write_message(0x0101, &[0x01, 0x02, 0x03, 0x04]);
assert_eq!(expected_message, generated_message.unwrap());
}
#[test]
fn new_write_message_data_too_long() {
let generated_message = Protocol300Message::try_new_write_message(0x0101, &vec![0x00; 300]);
assert!(generated_message.is_err());
assert!(matches!(generated_message.err().unwrap(), MessageCreationError::DataTooLong(_)));
}
#[test]
fn received_message_read_response_from_bytes() {
let expected_msg = Protocol300Message{
data_address: 0x5525,
telegram_length: 0x09,
function_code: FunctionCode::VirtualREAD,
message_identifier: MessageIdentifier::Response,
data_length: 0x04,
data: vec![0x07, 0x01, 0x27, 0x11],
checksum: 0xC9
};
let bytes = vec![0x41, 0x09, 0x01, 0x01, 0x55, 0x25, 0x04, 0x07, 0x01, 0x27, 0x11, 0xC9];
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
assert_eq!(expected_msg, message);
}
#[test]
fn received_message_write_response_from_bytes() {
let expected_msg = Protocol300Message{
data_address: 0x2101,
telegram_length: 0x05,
function_code: FunctionCode::VirtualWRITE,
message_identifier: MessageIdentifier::Response,
data_length: 0x04,
data: vec![],
checksum: 0x2E
};
let bytes = vec![0x41, 0x05, 0x01, 0x02, 0x21, 0x01, 0x04, 0x2E];
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
assert_eq!(expected_msg, message);
}
#[test]
fn received_message_read_request_from_bytes() {
let expected_msg = Protocol300Message{
data_address: 0x5525,
telegram_length: 0x05,
function_code: FunctionCode::VirtualREAD,
message_identifier: MessageIdentifier::Request,
data_length: 0x02,
data: vec![],
checksum: 0x82
};
let bytes = vec![0x41, 0x05, 0x00, 0x01, 0x55, 0x25, 0x02, 0x82];
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
assert_eq!(expected_msg, message);
}
#[test]
fn received_message_write_request_from_bytes() {
let expected_msg = Protocol300Message{
data_address: 0x2323,
telegram_length: 0x06,
function_code: FunctionCode::VirtualWRITE,
message_identifier: MessageIdentifier::Request,
data_length: 0x01,
data: vec![0x02],
checksum: 0x51
};
let bytes = vec![0x41, 0x06, 0x00, 0x02, 0x23, 0x23, 0x01, 0x02, 0x51];
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
assert_eq!(expected_msg, message);
}
// TO BYTES
#[test]
fn received_message_write_request_to_bytes() {
let test_msg = Protocol300Message{
data_address: 0x2323,
telegram_length: 0x06,
function_code: FunctionCode::VirtualWRITE,
message_identifier: MessageIdentifier::Request,
data_length: 0x01,
data: vec![0x02],
checksum: 0x51
};
let bytes_expected = vec![0x41, 0x06, 0x00, 0x02, 0x23, 0x23, 0x01, 0x02, 0x51];
let bytes_generated = test_msg.to_bytes();
assert_eq!(bytes_expected, bytes_generated);
}
#[test]
fn received_message_read_request_to_bytes() {
let test_msg = Protocol300Message{
data_address: 0x5525,
telegram_length: 0x05,
function_code: FunctionCode::VirtualREAD,
message_identifier: MessageIdentifier::Request,
data_length: 0x02,
data: vec![],
checksum: 0x82
};
let bytes_expected = vec![0x41, 0x05, 0x00, 0x01, 0x55, 0x25, 0x02, 0x82];
let bytes_generated = test_msg.to_bytes();
assert_eq!(bytes_expected, bytes_generated);
}
#[test]
fn received_message_write_response_to_bytes() {
let test_msg = Protocol300Message{
data_address: 0x2101,
telegram_length: 0x05,
function_code: FunctionCode::VirtualWRITE,
message_identifier: MessageIdentifier::Response,
data_length: 0x04,
data: vec![],
checksum: 0x2E
};
let bytes_expected = vec![0x41, 0x05, 0x01, 0x02, 0x21, 0x01, 0x04, 0x2E];
let bytes_generated = test_msg.to_bytes();
assert_eq!(bytes_expected, bytes_generated);
}
#[test]
fn received_message_read_response_to_bytes() {
let test_msg = Protocol300Message{
data_address: 0x5525,
telegram_length: 0x09,
function_code: FunctionCode::VirtualREAD,
message_identifier: MessageIdentifier::Response,
data_length: 0x04,
data: vec![0x07, 0x01, 0x27, 0x11],
checksum: 0xC9
};
let bytes_expected = vec![0x41, 0x09, 0x01, 0x01, 0x55, 0x25, 0x04, 0x07, 0x01, 0x27, 0x11, 0xC9];
let bytes_generated = test_msg.to_bytes();
assert_eq!(bytes_expected, bytes_generated);
}
#[test]
fn expected_return_length_read() {
let expected_message = Protocol300Message{
telegram_length: 5,
message_identifier: MessageIdentifier::Request,
function_code: FunctionCode::VirtualREAD,
data_address: 0x0101,
data_length: 2,
data: vec![],
checksum: 0x0A
};
assert_eq!(10_usize, expected_message.get_expected_return_telegram_length().unwrap());
}
#[test]
fn expected_return_length_write() {
let expected_message = Protocol300Message{
telegram_length: 9,
message_identifier: MessageIdentifier::Request,
function_code: FunctionCode::VirtualWRITE,
data_address: 0x0101,
data_length: 4,
data: vec![0x01, 0x02, 0x03, 0x04],
checksum: 0x1B
};
assert_eq!(8_usize, expected_message.get_expected_return_telegram_length().unwrap());
}
#[test]
fn create_read_response_message() {
let generated_message = Protocol300Message::new_read_response_message(0x5525, &[0x07, 0x01]).to_bytes();
let expected_message = vec![0x41, 0x07, 0x01, 0x01, 0x55, 0x25, 0x02, 0x07, 0x01, 0x8D];
assert_eq!(generated_message, expected_message);
}
#[test]
fn create_write_response_message() {
let generated_message = Protocol300Message::new_write_response_message(0x2323, 1).to_bytes();
let expected_message = vec![0x41, 0x05, 0x01, 0x02, 0x23, 0x23, 0x01, 0x4F];
assert_eq!(generated_message, expected_message);
}
}