mod error;
mod iter;
mod utils;
use {
crate::{
ocp1::pdu::{
Command, CommandResponseRequired, Handle, Header, KeepAlive, Message, MessageCount,
MessagePdu, Notification1, Notification1EventData, Notification1Parameters,
Notification2, Notification2ExceptionData, Notification2ExceptionType,
Notification2Type, Parameters, PduSize, PduType, ProtocolVersion, Response, SyncValue,
},
types::{OcaEvent, OcaEventId, OcaMethod, OcaMethodId, OcaONo, OcaStatus},
},
core::mem::size_of,
utils::ParseResultExt,
};
pub use {error::Error, iter::MessageIterator};
type Result<T> = core::result::Result<T, Error>;
pub fn message_pdu(input: &'_ [u8]) -> Result<(MessagePdu<'_>, &'_ [u8])> {
let (_sync_value, input) = sync_value(input)?;
let (header, input) = header(input)?;
let data_size = header
.pdu_size
.size_excluding_header()
.ok_or(Error::InvalidPduSize(header.pdu_size))?;
let (data, input) = utils::take(input, data_size)?;
Ok((MessagePdu { header, data }, input))
}
pub fn message(pdu_type: PduType, input: &'_ [u8]) -> Result<(Message<'_>, &'_ [u8])> {
match pdu_type {
PduType::Command => command(input).map_value(Message::Command),
PduType::CommandResponseRequired => command(input)
.map_value(CommandResponseRequired::from)
.map_value(Message::CommandResponseRequired),
PduType::Response => response(input).map_value(Message::Response),
PduType::Notification1 => notification1(input).map_value(Message::Notification1),
PduType::Notification2 => notification2(input).map_value(Message::Notification2),
PduType::KeepAlive => {
keep_alive(input).map(|keep_alive| (Message::KeepAlive(keep_alive), [].as_slice()))
}
}
}
pub fn command(input: &'_ [u8]) -> Result<(Command<'_>, &'_ [u8])> {
let (command, input) = take_message(input)?;
let (handle, command) = handle(command)?;
let (method, command) = method(command)?;
let parameters = parameters(command)?;
Ok((
Command {
handle,
method,
parameters,
},
input,
))
}
pub fn response(input: &'_ [u8]) -> Result<(Response<'_>, &'_ [u8])> {
let (response, input) = take_message(input)?;
let (handle, response) = handle(response)?;
let (status, response) = status(response)?;
let parameters = parameters(response)?;
Ok((
Response {
handle,
status,
parameters,
},
input,
))
}
pub fn notification1(input: &'_ [u8]) -> Result<(Notification1<'_>, &'_ [u8])> {
let (notification, input) = take_message(input)?;
let (target, notification) = method(notification)?;
let (count, notification) = utils::u8(notification)?;
if count != 2 {
return Err(Error::UnexpectedNotification1ParameterCount(count));
}
let (context, notification) = blob(notification)?;
let (event, notification) = event(notification)?;
let parameters = parameters(notification)?;
Ok((
Notification1 {
target,
parameters: Notification1Parameters {
context,
event_data: Notification1EventData { event, parameters },
},
},
input,
))
}
pub fn notification2(input: &'_ [u8]) -> Result<(Notification2<'_>, &'_ [u8])> {
let (notification, input) = take_message(input)?;
let (event, notification) = event(notification)?;
let (event_type, notification) = utils::u8(notification)?;
let event_type = match event_type {
0 => Notification2Type::Event(notification),
1 => {
let (data, _) = notification2_exception_data(notification)?;
Notification2Type::Exception(data)
}
_ => return Err(Error::UnexpectedNotification2Type(event_type)),
};
Ok((
Notification2 {
event,
r#type: event_type,
},
input,
))
}
pub fn keep_alive(input: &'_ [u8]) -> Result<KeepAlive> {
let (keep_alive, _) = match input.len() {
2 => utils::be_u16(input).map_value(KeepAlive::from)?,
4 => utils::be_u32(input).map_value(KeepAlive::from)?,
_ => return Err(Error::InvalidKeepAlivePayloadSize(input.len())),
};
Ok(keep_alive)
}
fn header(input: &'_ [u8]) -> Result<(Header, &'_ [u8])> {
let (protocol_version, input) = protocol_version(input)?;
let (pdu_size, input) = pdu_size(input)?;
let (pdu_type, input) = pdu_type(input)?;
let (message_count, input) = message_count(input)?;
if !message_count.is_valid_for_pdu_type(pdu_type) {
return Err(Error::InvalidMessageCountForPduType(
pdu_type,
message_count,
));
}
Ok((
Header {
protocol_version,
pdu_size,
pdu_type,
message_count,
},
input,
))
}
fn sync_value(input: &'_ [u8]) -> Result<(SyncValue, &'_ [u8])> {
utils::u8(input).try_map_value(SyncValue::try_from)
}
fn protocol_version(input: &'_ [u8]) -> Result<(ProtocolVersion, &'_ [u8])> {
utils::be_u16(input).try_map_value(ProtocolVersion::try_from)
}
fn pdu_size(input: &'_ [u8]) -> Result<(PduSize, &'_ [u8])> {
utils::be_u32(input).map_value(PduSize::from)
}
fn pdu_type(input: &'_ [u8]) -> Result<(PduType, &'_ [u8])> {
utils::u8(input).try_map_value(PduType::try_from)
}
fn message_count(input: &'_ [u8]) -> Result<(MessageCount, &'_ [u8])> {
utils::be_u16(input).try_map_value(MessageCount::try_from)
}
fn message_size(input: &'_ [u8]) -> Result<(usize, &'_ [u8])> {
let (message_size, input) = utils::be_u32(input).map_value(|size| size as usize)?;
let message_size_excluding_self = message_size.saturating_sub(size_of::<u32>());
if input.len() < message_size_excluding_self {
return Err(Error::InvalidMessageSize(message_size));
}
Ok((message_size_excluding_self, input))
}
fn take_message(input: &'_ [u8]) -> Result<(&'_ [u8], &'_ [u8])> {
message_size(input).and_then(|(message_size, input)| utils::take(input, message_size))
}
fn handle(input: &'_ [u8]) -> Result<(Handle, &'_ [u8])> {
utils::be_u32(input).map_value(Handle::from)
}
fn ono(input: &'_ [u8]) -> Result<(OcaONo, &'_ [u8])> {
utils::be_u32(input).map_value(OcaONo::from)
}
fn method_id(input: &'_ [u8]) -> Result<(OcaMethodId, &'_ [u8])> {
let (level, input) = utils::be_u16(input)?;
let (index, input) = utils::be_u16(input)?;
Ok((OcaMethodId { level, index }, input))
}
fn method(input: &'_ [u8]) -> Result<(OcaMethod, &'_ [u8])> {
let (object, input) = ono(input)?;
let (id, input) = method_id(input)?;
Ok((OcaMethod { object, id }, input))
}
fn parameters(input: &'_ [u8]) -> Result<Parameters<'_>> {
let (count, parameters) = utils::u8(input)?;
Ok(Parameters { count, parameters })
}
fn status(input: &'_ [u8]) -> Result<(OcaStatus, &'_ [u8])> {
utils::u8(input).map_value(OcaStatus::from)
}
fn event_id(input: &'_ [u8]) -> Result<(OcaEventId, &'_ [u8])> {
let (level, input) = utils::be_u16(input)?;
let (index, input) = utils::be_u16(input)?;
Ok((OcaEventId { level, index }, input))
}
fn event(input: &'_ [u8]) -> Result<(OcaEvent, &'_ [u8])> {
let (object, input) = ono(input)?;
let (id, input) = event_id(input)?;
Ok((OcaEvent { object, id }, input))
}
fn blob(input: &'_ [u8]) -> Result<(&'_ [u8], &'_ [u8])> {
let (size, input) = utils::be_u16(input).map_value(|size| size as usize)?;
let (blob, input) = utils::take(input, size)?;
Ok((blob, input))
}
fn notification2_exception_type(input: &'_ [u8]) -> Result<(Notification2ExceptionType, &'_ [u8])> {
utils::u8(input).map_value(Notification2ExceptionType::from)
}
fn bool(input: &'_ [u8]) -> Result<(bool, &'_ [u8])> {
utils::u8(input).map_value(|value| value != 0)
}
fn notification2_exception_data(
input: &'_ [u8],
) -> Result<(Notification2ExceptionData<'_>, &'_ [u8])> {
let (exception_type, input) = notification2_exception_type(input)?;
let (try_again, input) = bool(input)?;
let (data, input) = blob(input)?;
Ok((
Notification2ExceptionData {
r#type: exception_type,
try_again,
data,
},
input,
))
}
#[cfg(test)]
mod tests {
use {super::*, crate::ocp1::pdu::Header, alloc::vec::Vec, core::assert_matches};
struct MessageSpec<'a> {
sync: u8,
version: u16,
size: Option<u32>,
pdu_type: u8,
count: u16,
data: &'a [u8],
}
impl<'a> Default for MessageSpec<'a> {
fn default() -> Self {
MessageSpec {
sync: SyncValue::VALUE,
version: u16::from(ProtocolVersion::V1),
size: None,
pdu_type: PduType::Command as u8,
count: 1,
data: &[],
}
}
}
impl<'a> MessageSpec<'a> {
fn to_bytes(&self) -> Vec<u8> {
let size = self.size.unwrap_or((Header::SIZE + self.data.len()) as u32);
let mut bytes = Vec::new();
bytes.push(self.sync);
bytes.extend_from_slice(&self.version.to_be_bytes());
bytes.extend_from_slice(&size.to_be_bytes());
bytes.push(self.pdu_type);
bytes.extend_from_slice(&self.count.to_be_bytes());
bytes.extend_from_slice(self.data);
bytes
}
}
macro_rules! field_value {
(size, $value:expr) => {
Some($value)
};
(parameter_count, $value:expr) => {
Some($value)
};
(pdu_type, $value:expr) => {
$value as u8
};
($field:ident, $value:expr) => {
$value
};
}
macro_rules! message {
($($field:ident: $value:expr),* $(,)?) => {
MessageSpec {
$($field: field_value!($field, $value),)*
..Default::default()
}
.to_bytes()
};
}
struct CommandSpec<'a> {
size: Option<u32>,
handle: u32,
object: u32,
level: u16,
index: u16,
parameter_count: Option<u8>,
parameters: &'a [u8],
}
impl<'a> Default for CommandSpec<'a> {
fn default() -> Self {
CommandSpec {
size: None,
handle: 1,
object: 1,
level: 1,
index: 1,
parameter_count: None,
parameters: &[],
}
}
}
impl<'a> CommandSpec<'a> {
fn to_bytes(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.handle.to_be_bytes());
body.extend_from_slice(&self.object.to_be_bytes());
body.extend_from_slice(&self.level.to_be_bytes());
body.extend_from_slice(&self.index.to_be_bytes());
body.push(self.parameter_count.unwrap_or(self.parameters.len() as u8));
body.extend_from_slice(self.parameters);
let size = self.size.unwrap_or((size_of::<u32>() + body.len()) as u32);
let mut bytes = Vec::new();
bytes.extend_from_slice(&size.to_be_bytes());
bytes.extend_from_slice(&body);
bytes
}
}
macro_rules! command {
($($field:ident: $value:expr),* $(,)?) => {
CommandSpec {
$($field: field_value!($field, $value),)*
..Default::default()
}
.to_bytes()
};
}
struct ResponseSpec<'a> {
size: Option<u32>,
handle: u32,
status: u8,
parameter_count: Option<u8>,
parameters: &'a [u8],
}
impl<'a> Default for ResponseSpec<'a> {
fn default() -> Self {
ResponseSpec {
size: None,
handle: 1,
status: 0,
parameter_count: None,
parameters: &[],
}
}
}
impl<'a> ResponseSpec<'a> {
fn to_bytes(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.handle.to_be_bytes());
body.push(self.status);
body.push(self.parameter_count.unwrap_or(self.parameters.len() as u8));
body.extend_from_slice(self.parameters);
let size = self.size.unwrap_or((size_of::<u32>() + body.len()) as u32);
let mut bytes = Vec::new();
bytes.extend_from_slice(&size.to_be_bytes());
bytes.extend_from_slice(&body);
bytes
}
}
macro_rules! response {
($($field:ident: $value:expr),* $(,)?) => {
ResponseSpec {
$($field: field_value!($field, $value),)*
..Default::default()
}
.to_bytes()
};
}
#[test]
fn parses_a_valid_message() {
let bytes = message! { data: b"Hello, world!" };
let (msg, remaining) = message_pdu(bytes.as_slice()).unwrap();
assert_eq!(
msg.header,
Header {
protocol_version: ProtocolVersion::V1,
pdu_size: PduSize::from(22),
pdu_type: PduType::Command,
message_count: MessageCount::try_from(1).unwrap(),
}
);
assert_eq!(msg.data, b"Hello, world!");
assert!(remaining.is_empty());
}
#[test]
fn leaves_trailing_bytes_unconsumed() {
let mut bytes = message! { data: b"hello" };
bytes.extend_from_slice(b"next message");
let (msg, remaining) = message_pdu(bytes.as_slice()).unwrap();
assert_eq!(msg.data, b"hello");
assert_eq!(remaining, b"next message");
}
#[test]
fn rejects_wrong_sync_value() {
assert_matches!(
message_pdu(message! { sync: 0xFF }.as_slice()),
Err(Error::InvalidSyncValue(_))
);
}
#[test]
fn rejects_unknown_protocol_version() {
assert_matches!(
message_pdu(message! { version: 2 }.as_slice()),
Err(Error::UnexpectedProtocolVersion(_))
);
}
#[test]
fn rejects_unknown_pdu_type() {
assert_matches!(
message_pdu(message! { pdu_type: 100 }.as_slice()),
Err(Error::UnexpectedPduType(_))
);
}
#[test]
fn rejects_zero_message_count() {
assert_matches!(
message_pdu(message! { count: 0 }.as_slice()),
Err(Error::InvalidMessageCount(_))
);
}
#[test]
fn rejects_keep_alive_with_count_other_than_one() {
let bytes = message! {
pdu_type: PduType::KeepAlive,
count: 2,
};
assert_matches!(
message_pdu(bytes.as_slice()),
Err(Error::InvalidMessageCountForPduType(_, _))
);
}
#[test]
fn rejects_pdu_size_smaller_than_header() {
assert_matches!(
message_pdu(message! { size: 0 }.as_slice()),
Err(Error::InvalidPduSize(_))
);
}
#[test]
fn rejects_pdu_size_larger_than_available_data() {
let bytes = message! {
size: 1000,
data: b"hello",
};
assert_matches!(message_pdu(bytes.as_slice()), Err(Error::IncompleteInput));
}
#[test]
fn rejects_truncated_input_at_every_stage() {
let bytes = message! { data: b"hello" };
for len in 0..bytes.len() {
assert!(
message_pdu(&bytes[..len]).is_err(),
"len {len} should not parse"
);
}
}
#[test]
fn parses_a_valid_command() {
let bytes = command! {
handle: 42,
object: 7,
level: 2,
index: 3,
parameters: b"abc",
};
let (cmd, remaining) = command(bytes.as_slice()).unwrap();
assert_eq!(cmd.handle, Handle::from(42));
assert_eq!(cmd.method.object, OcaONo::from(7));
assert_eq!(cmd.method.id.level, 2);
assert_eq!(cmd.method.id.index, 3);
assert_eq!(cmd.parameters.count, 3);
assert_eq!(cmd.parameters.parameters, b"abc");
assert!(remaining.is_empty());
}
#[test]
fn leaves_trailing_bytes_unconsumed_after_command() {
let mut bytes = command! { parameters: b"abc" };
bytes.extend_from_slice(b"next message");
let (cmd, remaining) = command(bytes.as_slice()).unwrap();
assert_eq!(cmd.parameters.parameters, b"abc");
assert_eq!(remaining, b"next message");
}
#[test]
fn rejects_command_with_size_larger_than_available_data() {
let bytes = command! {
size: 1000,
parameters: b"abc",
};
assert_matches!(command(bytes.as_slice()), Err(Error::InvalidMessageSize(_)));
}
#[test]
fn rejects_truncated_command_input_at_every_stage() {
let bytes = command! { parameters: b"abc" };
for len in 0..bytes.len() {
assert!(
command(&bytes[..len]).is_err(),
"len {len} should not parse"
);
}
}
#[test]
fn parses_a_valid_response() {
let bytes = response! {
handle: 42,
status: 0,
parameters: b"abc",
};
let (resp, remaining) = response(bytes.as_slice()).unwrap();
assert_eq!(resp.handle, Handle::from(42));
assert!(matches!(resp.status, OcaStatus::Ok));
assert_eq!(resp.parameters.count, 3);
assert_eq!(resp.parameters.parameters, b"abc");
assert!(remaining.is_empty());
}
#[test]
fn parses_a_response_with_an_error_status() {
let bytes = response! { status: 5 };
let (resp, _) = response(bytes.as_slice()).unwrap();
assert_matches!(resp.status, OcaStatus::Err(5));
}
#[test]
fn leaves_trailing_bytes_unconsumed_after_response() {
let mut bytes = response! { parameters: b"abc" };
bytes.extend_from_slice(b"next message");
let (resp, remaining) = response(bytes.as_slice()).unwrap();
assert_eq!(resp.parameters.parameters, b"abc");
assert_eq!(remaining, b"next message");
}
#[test]
fn rejects_response_with_size_larger_than_available_data() {
let bytes = response! {
size: 1000,
parameters: b"abc",
};
assert_matches!(
response(bytes.as_slice()),
Err(Error::InvalidMessageSize(_))
);
}
#[test]
fn rejects_truncated_response_input_at_every_stage() {
let bytes = response! { parameters: b"abc" };
for len in 0..bytes.len() {
assert!(
response(&bytes[..len]).is_err(),
"len {len} should not parse"
);
}
}
}