mod error;
pub use error::Error;
use {
crate::{
ocp1::{
pdu::{
Command, CommandResponseRequired, Handle, Header, IsMessage, KeepAlive, Message,
MessageCount, MessagePdu, Notification1, Notification2, Notification2Type,
Parameters, PduType, ProtocolVersion, Response, SyncValue,
},
utils::writer::{Reserved, Writer},
},
types::{OcaEvent, OcaMethod},
},
core::{marker::PhantomData, mem::size_of},
};
type Result<T> = core::result::Result<T, Error>;
pub fn encode_pdu<'a>(message_pdu: &MessagePdu<'_>, buffer: &'a mut [u8]) -> Result<&'a [u8]> {
let mut writer = Writer::new(buffer);
writer.write_u8(SyncValue::VALUE)?;
writer.write_header(&message_pdu.header)?;
writer.write_bytes(message_pdu.data)?;
let (encoded, _) = writer.finish();
Ok(encoded)
}
pub fn encode_message<'a, T>(message: &T, buffer: &'a mut [u8]) -> Result<&'a [u8]>
where
T: Encode + IsMessage,
{
let mut encoder = Encoder::<T>::new(buffer)?;
encoder.encode(message)?;
encoder.finish()
}
pub fn encode_messages<'a, T>(messages: &[T], buffer: &'a mut [u8]) -> Result<&'a [u8]>
where
T: Encode + IsMessage,
{
let mut encoder = Encoder::<T>::new(buffer)?;
for message in messages {
encoder.encode(message)?;
}
encoder.finish()
}
struct Encoder<'a, T> {
writer: Writer<'a>,
header: Reserved<{ Header::SIZE }>,
message_count: u16,
pdu_type: Option<PduType>,
_marker: PhantomData<fn(&T)>,
}
impl<'a, T> Encoder<'a, T> {
pub fn new(buffer: &'a mut [u8]) -> Result<Self> {
let mut writer = Writer::new(buffer);
writer.write_u8(SyncValue::VALUE)?;
let header = writer.reserve::<{ Header::SIZE }>()?;
Ok(Self {
writer,
header,
message_count: 0,
pdu_type: None,
_marker: PhantomData,
})
}
}
impl<'a, T> Encoder<'a, T>
where
T: Encode,
{
pub fn encode(&mut self, message: &T) -> Result<()> {
let len = {
let (encoded, _) = message.encode(self.writer.remaining())?;
encoded.len()
};
self.writer.commit(len);
self.message_count += 1;
match self.pdu_type {
Some(pdu_type) => {
if pdu_type != message.pdu_type() {
return Err(Error::MismatchedPduType {
expected: pdu_type,
found: message.pdu_type(),
});
}
}
None => {
self.pdu_type = Some(message.pdu_type());
}
}
Ok(())
}
pub fn finish(mut self) -> Result<&'a [u8]> {
let pdu_type = self.pdu_type.ok_or(Error::NoMessages)?;
let pdu_size = self.writer.len().saturating_sub(1);
let header_slice = self.writer.claim(self.header)?;
Writer::new(header_slice).write_header(&Header {
protocol_version: ProtocolVersion::V1,
pdu_size: u32::try_from(pdu_size)
.map_err(|_| Error::DataTooLarge)?
.into(),
pdu_type,
message_count: MessageCount::try_from(self.message_count)?,
})?;
let (encoded, _) = self.writer.finish();
Ok(encoded)
}
}
pub trait Encode: sealed::Sealed {
fn pdu_type(&self) -> PduType;
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])>;
}
mod sealed {
use super::*;
pub trait Sealed {}
impl Sealed for Message<'_> {}
impl Sealed for Command<'_> {}
impl Sealed for CommandResponseRequired<'_> {}
impl Sealed for Response<'_> {}
impl Sealed for Notification1<'_> {}
impl Sealed for Notification2<'_> {}
impl Sealed for KeepAlive {}
}
impl Encode for Message<'_> {
fn pdu_type(&self) -> PduType {
Message::pdu_type(self)
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
match self {
Message::Command(command) => command.encode(out),
Message::CommandResponseRequired(command) => command.encode(out),
Message::Response(response) => response.encode(out),
Message::Notification1(notification) => notification.encode(out),
Message::Notification2(notification) => notification.encode(out),
Message::KeepAlive(keep_alive) => keep_alive.encode(out),
}
}
}
impl Encode for Command<'_> {
fn pdu_type(&self) -> PduType {
PduType::Command
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
command(self, out)
}
}
impl Encode for CommandResponseRequired<'_> {
fn pdu_type(&self) -> PduType {
PduType::CommandResponseRequired
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
command(self.as_ref(), out)
}
}
impl Encode for Response<'_> {
fn pdu_type(&self) -> PduType {
PduType::Response
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
response(self, out)
}
}
impl Encode for Notification1<'_> {
fn pdu_type(&self) -> PduType {
PduType::Notification1
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
notification1(self, out)
}
}
impl Encode for Notification2<'_> {
fn pdu_type(&self) -> PduType {
PduType::Notification2
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
notification2(self, out)
}
}
impl Encode for KeepAlive {
fn pdu_type(&self) -> PduType {
PduType::KeepAlive
}
fn encode<'a>(&self, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
keep_alive(self, out)
}
}
fn command<'a>(command: &Command<'_>, out: &'a mut [u8]) -> Result<(&'a mut [u8], &'a mut [u8])> {
let mut writer = Writer::new(out);
writer.write_message(|buffer| {
buffer.write_handle(command.handle)?;
buffer.write_method(&command.method)?;
buffer.write_parameters(&command.parameters)?;
Ok(())
})?;
Ok(writer.finish())
}
fn response<'a>(
response: &Response<'_>,
out: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8])> {
let mut writer = Writer::new(out);
writer.write_message(|writer| {
writer.write_handle(response.handle)?;
writer.write_u8(response.status.into())?;
writer.write_parameters(&response.parameters)?;
Ok(())
})?;
Ok(writer.finish())
}
fn notification1<'a>(
notification: &Notification1<'_>,
out: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8])> {
let mut writer = Writer::new(out);
writer.write_message(|writer| {
writer.write_method(¬ification.target)?;
writer.write_u8(2)?;
writer.write_blob(notification.parameters.context)?;
writer.write_event(¬ification.parameters.event_data.event)?;
writer.write_parameters(¬ification.parameters.event_data.parameters)?;
Ok(())
})?;
Ok(writer.finish())
}
fn notification2<'a>(
notification: &Notification2<'_>,
out: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8])> {
let mut writer = Writer::new(out);
writer.write_message(|writer| {
writer.write_event(¬ification.event)?;
match ¬ification.r#type {
Notification2Type::Event(data) => {
writer.write_u8(0)?;
writer.write_bytes(data)?;
Ok(())
}
Notification2Type::Exception(data) => {
writer.write_u8(1)?;
writer.write_u8(data.r#type.into())?;
writer.write_u8(if data.try_again { 1 } else { 0 })?;
writer.write_blob(data.data)?;
Ok(())
}
}
})?;
Ok(writer.finish())
}
fn keep_alive<'a>(
keep_alive: &KeepAlive,
out: &'a mut [u8],
) -> Result<(&'a mut [u8], &'a mut [u8])> {
let mut writer = Writer::new(out);
match keep_alive {
KeepAlive::Seconds(value) => writer.write_u16(*value)?,
KeepAlive::Milliseconds(value) => writer.write_u32(*value)?,
}
Ok(writer.finish())
}
trait WriterExt {
fn write_header(&mut self, header: &Header) -> Result<()>;
fn write_message(
&mut self,
write_message: impl FnOnce(&mut Writer) -> Result<()>,
) -> Result<()>;
fn write_handle(&mut self, handle: Handle) -> Result<()>;
fn write_parameters(&mut self, parameters: &Parameters) -> Result<()>;
fn write_method(&mut self, method: &OcaMethod) -> Result<()>;
fn write_event(&mut self, event: &OcaEvent) -> Result<()>;
fn write_blob(&mut self, blob: &[u8]) -> Result<()>;
}
impl WriterExt for Writer<'_> {
fn write_header(&mut self, header: &Header) -> Result<()> {
self.write_u16(header.protocol_version.into())?;
self.write_u32(header.pdu_size.into())?;
self.write_u8(header.pdu_type.into())?;
self.write_u16(header.message_count.into())?;
Ok(())
}
fn write_message(
&mut self,
write_message: impl FnOnce(&mut Writer) -> Result<()>,
) -> Result<()> {
let size_field = self.reserve::<{ size_of::<u32>() }>()?;
let before = self.len();
write_message(self)?;
let size = (self.len() - before) + size_of::<u32>();
let size: u32 = size.try_into().map_err(|_| Error::DataTooLarge)?;
self.apply(size_field, size.to_be_bytes())?;
Ok(())
}
fn write_handle(&mut self, handle: Handle) -> Result<()> {
Ok(self.write_u32(handle.into())?)
}
fn write_parameters(&mut self, parameters: &Parameters) -> Result<()> {
self.write_u8(parameters.count)?;
self.write_bytes(parameters.parameters)?;
Ok(())
}
fn write_method(&mut self, method: &OcaMethod) -> Result<()> {
self.write_u32(method.object.into())?;
self.write_u16(method.id.level)?;
self.write_u16(method.id.index)?;
Ok(())
}
fn write_event(&mut self, event: &OcaEvent) -> Result<()> {
self.write_u32(event.object.into())?;
self.write_u16(event.id.level)?;
self.write_u16(event.id.index)?;
Ok(())
}
fn write_blob(&mut self, blob: &[u8]) -> Result<()> {
self.write_u16(blob.len() as u16)?;
self.write_bytes(blob)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
ocp1::{
parser,
pdu::{PduSize, PduType},
utils::writer::Error as WriterError,
},
types::{OcaMethodId, OcaONo},
},
core::assert_matches,
};
#[test]
fn encoding_no_messages_is_an_error() {
let mut buffer = [0; 512];
let encoder = Encoder::<Command>::new(&mut buffer).unwrap();
assert_matches!(encoder.finish(), Err(Error::NoMessages))
}
#[test]
fn encoding_a_single_message() {
let mut buffer = [0; 512];
let mut encoder = Encoder::<Command>::new(&mut buffer).unwrap();
let original = Command {
handle: 100.into(),
method: OcaMethod {
object: 500.into(),
id: (1, 1).into(),
},
parameters: Parameters {
count: 0,
parameters: &[],
},
};
encoder.encode(&original).unwrap();
let encoded = encoder.finish().unwrap();
let (message, _) = parser::message_pdu(encoded).unwrap();
assert_eq!(message.header.protocol_version, ProtocolVersion::V1);
assert_eq!(message.header.pdu_size, PduSize::from(26));
assert_eq!(message.header.pdu_type, PduType::Command);
assert_eq!(
message.header.message_count,
MessageCount::try_from(1).unwrap()
);
let (command, _) = parser::command(message.data).unwrap();
assert_eq!(command, original);
}
#[test]
fn encoding_multiple_messages() {
let mut buffer = [0; 512];
let mut encoder = Encoder::<Command>::new(&mut buffer).unwrap();
let commands = [
Command {
handle: 100.into(),
method: OcaMethod {
object: 500.into(),
id: (1, 1).into(),
},
parameters: Parameters {
count: 0,
parameters: &[],
},
},
Command {
handle: 101.into(),
method: OcaMethod {
object: 501.into(),
id: (1, 3).into(),
},
parameters: Parameters {
count: 2,
parameters: b"foobar",
},
},
];
encoder.encode(&commands[0]).unwrap();
encoder.encode(&commands[1]).unwrap();
let encoded = encoder.finish().unwrap();
let (message, _) = parser::message_pdu(encoded).unwrap();
assert_eq!(message.header.protocol_version, ProtocolVersion::V1);
assert_eq!(message.header.pdu_size, PduSize::from(49));
assert_eq!(message.header.pdu_type, PduType::Command);
assert_eq!(
message.header.message_count,
MessageCount::try_from(2).unwrap()
);
let (command, leftover) = parser::command(message.data).unwrap();
assert_eq!(command, commands[0]);
let (command, _) = parser::command(leftover).unwrap();
assert_eq!(command, commands[1]);
}
#[test]
fn rejects_buffer_too_small_for_a_command() {
let mut buffer = [0u8; 3];
let result = Command {
handle: Handle::from(1),
method: OcaMethod {
object: OcaONo::from(1),
id: OcaMethodId { level: 1, index: 1 },
},
parameters: Parameters {
count: 0,
parameters: &[],
},
}
.encode(&mut buffer);
assert_matches!(result, Err(Error::WriterError(WriterError::BufferFull)));
}
}