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 eca6111
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 6 deletions.
100 changes: 100 additions & 0 deletions src/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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();
}
}
5 changes: 2 additions & 3 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
27 changes: 26 additions & 1 deletion 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 All @@ -17,4 +42,4 @@ where
Self: Sized,
{
fn deserialize(buffer: &[u8]) -> Result<Self, &'static str>;
}
}
21 changes: 19 additions & 2 deletions tests/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,38 @@ use std::convert::TryFrom;

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

#[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 eca6111

Please sign in to comment.