Created
May 8, 2019 04:03
-
-
Save evaporei/98c5e5c8ae63cc4b829b7c211d20b49e to your computer and use it in GitHub Desktop.
How to parse array of bytes using a state machine (in Rust!)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct State { | |
step: Step, | |
data: Vec<u8>, | |
} | |
impl State { | |
pub fn new() -> Self { | |
Self { step: Step::Initial, data: vec![] } | |
} | |
pub fn parse(&mut self, data: Vec<u8>) -> Result<(), Error> { | |
// This works like a cursor or a state machine with the step enum | |
for byte in &data { | |
match self.step { | |
Step::Initial => { | |
if *byte != 1 { | |
return Err(Error::WrongInitialByte); | |
} | |
self.step = Step::Middle; | |
} | |
Step::Middle => { | |
if self.data.len() > 3 { | |
return Err(Error::DataOverflow); | |
} | |
if self.data.len() == 3 { | |
if *byte != 100 { | |
return Err(Error::WrongFinalByte); | |
} | |
self.step = Step::Finished; | |
} | |
self.data.push(*byte); | |
} | |
Step::Finished => {} | |
} | |
} | |
if self.step != Step::Finished { | |
return Ok(()); | |
} | |
self.process(); | |
Ok(()) | |
} | |
pub fn process(&mut self) { | |
// do any logic with complete packet/data | |
// then empty it | |
self.data.clear(); | |
// reset, to read more packets | |
self.step = Step::Initial; | |
} | |
} | |
#[derive(PartialEq)] | |
enum Step { | |
Initial, | |
Middle, | |
Finished, | |
} | |
#[derive(Debug, PartialEq)] | |
enum Error { | |
WrongInitialByte, | |
DataOverflow, | |
WrongFinalByte, | |
} | |
fn main() { | |
// Let's consider a binary protocol that is based of a list of bytes of length 5. | |
// The first byte sent is a `1`, and the last, a `100`. | |
// The ones in the middle don't really matter which numbers they are, however | |
// they have the main content of the delivery. | |
// Here we create a state struct, that has no data in it (3 significant bytes) | |
// of our fictitious protocol. Also, it hasn't parsed anything, so it is on the | |
// initial step. | |
let mut state = State::new(); | |
// Here is an example of receiving the full packet at once: | |
state.parse(vec![1, 22, 42, 33, 100]).unwrap(); | |
// Here, we receive the packet in parts. | |
// The great thing is that this state machine allows you | |
// to call `parse` how many times needed, this is useful when dealing | |
// with separate deliveries of something sent via USB or Bluetooth, for example. | |
// This works much like a cursor. | |
state.parse(vec![1, 22]).unwrap(); | |
state.parse(vec![32, 44]).unwrap(); | |
state.parse(vec![100]).unwrap(); | |
// passing the wrong first byte (not 1) | |
assert_eq!(state.parse(vec![55]), Err(Error::WrongInitialByte)); | |
// editing `data` without `parse` | |
state.parse(vec![1]).unwrap(); | |
state.parse(vec![74, 26, 31]).unwrap(); | |
state.data.push(25); | |
assert_eq!(state.parse(vec![100]), Err(Error::DataOverflow)); | |
// reset because of DataOverflow error | |
state.data.clear(); | |
state.step = Step::Initial; | |
// passing the wrong final byte (not 100) | |
state.parse(vec![1]).unwrap(); | |
state.parse(vec![74, 26, 31]).unwrap(); | |
assert_eq!(state.parse(vec![73]), Err(Error::WrongFinalByte)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment