Skip to content

Commit

Permalink
Add decoder
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick José Pereira <[email protected]>
  • Loading branch information
patrickelectric committed Dec 24, 2023
1 parent f4a6187 commit ea65135
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 7 deletions.
101 changes: 101 additions & 0 deletions src/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::message::{ProtocolMessage, HEADER};

#[derive(Debug)]
pub enum ParseError {
InvalidStartByte,
IncompleteData,
ChecksumError,
}

#[derive(Debug)]
pub enum DecoderResult {
Success(ProtocolMessage),
InProgress,
Error(ParseError),
}

#[derive(Debug)]
pub enum DecoderState {
AwaitingStart1,
AwaitingStart2,
ReadingHeader,
ReadingPayload,
ReadingChecksum,
}

pub struct Decoder {
pub state: DecoderState,
buffer: Vec<u8>,
message: ProtocolMessage,
}

impl Decoder {
pub fn new() -> Self {
Self {
state: DecoderState::AwaitingStart1,
buffer: Vec::new(),
message: ProtocolMessage::new(),
}
}

pub fn parse_byte(&mut self, byte: u8) -> DecoderResult {
match self.state {
DecoderState::AwaitingStart1 => {
if byte == HEADER[0] {
self.state = DecoderState::AwaitingStart2;
return DecoderResult::InProgress;
}
return DecoderResult::Error(ParseError::InvalidStartByte);
}
DecoderState::AwaitingStart2 => {
if byte == HEADER[1] {
self.state = DecoderState::ReadingHeader;
self.buffer.clear();
return DecoderResult::InProgress;
}
self.state = DecoderState::AwaitingStart1;
return DecoderResult::Error(ParseError::InvalidStartByte);
}
DecoderState::ReadingHeader => {
self.buffer.push(byte);
// Basic information is available, moving to payload state
if self.buffer.len() == 6 {
self.message.payload_length =
u16::from_le_bytes([self.buffer[0], self.buffer[1]]);
self.message.message_id = u16::from_le_bytes([self.buffer[2], self.buffer[3]]);
self.message.src_device_id = self.buffer[4];
self.message.dst_device_id = self.buffer[5];
self.state = DecoderState::ReadingPayload;
self.buffer.clear();
}
return DecoderResult::InProgress;
}
DecoderState::ReadingPayload => {
self.buffer.push(byte);
dbg!(self.buffer.len(), self.message.payload_length);
if self.buffer.len() == self.message.payload_length as usize {
self.message.payload = self.buffer.clone();
self.state = DecoderState::ReadingChecksum;
self.buffer.clear();
}
return DecoderResult::InProgress;
}
DecoderState::ReadingChecksum => {
self.buffer.push(byte);
if self.buffer.len() == 2 {
self.message.checksum = u16::from_le_bytes([self.buffer[0], self.buffer[1]]);
let message = self.message.clone();
self.message = ProtocolMessage::new();
self.reset();
return DecoderResult::Success(message);
}
return DecoderResult::InProgress;
}
}
}

fn reset(&mut self) {
self.state = DecoderState::AwaitingStart1;
self.buffer.clear();
}
}
8 changes: 3 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

use crate::message::{Deserialize, PingMessage};
use crate::message::{Deserialize, PingMessage, HEADER};

const PAYLOAD_SIZE: usize = 255;

use std::fmt;
use std::{convert::TryFrom, io::Write};

pub mod decoder;
pub mod message;

pub const HEADER: [u8; 2] = ['B' as u8, 'R' as u8];

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct PingMessagePack([u8; 1 + Self::HEADER_SIZE + PAYLOAD_SIZE + 2]);

Expand Down Expand Up @@ -49,8 +48,7 @@ impl TryFrom<&Vec<u8>> for Messages {

fn try_from(buffer: &Vec<u8>) -> Result<Self, Self::Error> {
// Parse start1 and start2
if !((buffer[0] == HEADER[0]) && (buffer[1] == HEADER[1]))
{
if !((buffer[0] == HEADER[0]) && (buffer[1] == HEADER[1])) {
return Err(format!("Message should start with \"BR\" ASCII sequence, received: [{0}({:0x}), {1}({:0x})]", buffer[0], buffer[1]));
}

Expand Down
25 changes: 25 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
pub const HEADER: [u8; 2] = ['B' as u8, 'R' as u8];

#[derive(Clone, Debug)]
pub struct ProtocolMessage {
pub payload_length: u16,
pub message_id: u16,
pub src_device_id: u8,
pub dst_device_id: u8,
pub payload: Vec<u8>,
pub checksum: u16,
}

impl ProtocolMessage {
pub fn new() -> Self {
ProtocolMessage {
payload_length: 0,
message_id: 0,
src_device_id: 0,
dst_device_id: 0,
payload: Vec::new(),
checksum: 0,
}
}
}

pub trait PingMessage
where
Self: Sized + Serialize + Deserialize,
Expand Down
26 changes: 24 additions & 2 deletions tests/deserialize.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
use std::convert::TryFrom;

use ping_rs::common::Messages as common_messages;
use ping_rs::decoder::*;
use ping_rs::{common, Messages};

#[test]
fn test_simple_deserialization() {
let mut decoder = Decoder::new();
let general_request =
common_messages::GeneralRequest(common::GeneralRequestStruct { requested_id: 5 });

// From official ping protocol documentation
let buffer: Vec<u8> = vec![
0x42, 0x52, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0xa1, 0x00,
0x42, 0x52, 0x02, 0x00, // payload length
0x06, 0x00, // message id
0x00, 0x00, // src and dst id
0x05, 0x00, // payload
0xa1, 0x00, // crc
];
let Messages::Common(parsed) = Messages::try_from(&buffer).unwrap() else {
panic!("Failed to parse common message.");
};
// From official ping protocol documentation
assert_eq!(general_request, parsed);

for byte in &buffer[0..buffer.len() - 2] {
dbg!(byte, &decoder.state);
assert!(matches!(
decoder.parse_byte(byte.clone()),
DecoderResult::InProgress
));
}
assert!(matches!(
decoder.parse_byte(buffer[buffer.len() - 2]),
DecoderResult::InProgress
));
let DecoderResult::Success(_message) = decoder.parse_byte(buffer[buffer.len()-1]) else {
dbg!(decoder.state);
panic!("Failed to use decoder with valid message");
};

// Wrong CRC test
let buffer: Vec<u8> = vec![
0x42, 0x52, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0xa1, 0x01,
Expand Down

0 comments on commit ea65135

Please sign in to comment.