Skip to content

Commit

Permalink
Merge pull request #227 from TheNeikos/feature/add_v5_parsing
Browse files Browse the repository at this point in the history
Feature/add v5 parsing
  • Loading branch information
TheNeikos authored Mar 20, 2024
2 parents 2def12f + 52f3208 commit a068335
Show file tree
Hide file tree
Showing 31 changed files with 2,624 additions and 392 deletions.
812 changes: 425 additions & 387 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ bytes = "1.4.0"
clap = { version = "4.2.1", optional = true, features = ["derive"] }
dashmap = "5.4.0"
futures = "0.3.28"
mqtt-format = { version = "0.5.0", path = "mqtt-format", features = ["yoke"] }
mqtt-format = { version = "0.5.0", path = "mqtt-format", features = ["yoke", "mqttv3"] }
nom = { version = "7.1.3" }
thiserror = "1.0.40"
tokio = { version = "1.27.0", default-features = false, features = [
Expand Down
12 changes: 9 additions & 3 deletions mqtt-format/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ categories = ["embedded", "parsing"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["mqttv5"]
yoke = ["dep:yoke"]
debug = ["winnow/debug"]
mqttv3 = ["dep:futures", "dep:nom", "dep:nom-supreme"]
mqttv5 = ["dep:winnow"]

[dependencies]
futures = "0.3.28"
nom = "7.1.3"
nom-supreme = "0.8.0"
futures = { version = "0.3.28", optional = true }
nom = { version = "7.1.3", optional = true }
nom-supreme = { version = "0.8.0", optional = true }
num_enum = "0.7.2"
thiserror = "1.0.40"
winnow = { version = "0.6.5", optional = true }
yoke = { version = "0.7.0", features = ["derive"], optional = true }

[dev-dependencies]
Expand Down
1 change: 0 additions & 1 deletion mqtt-format/clippy.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
disallowed-methods = [
"std::result::Result::unwrap",
"std::result::Result::expect",
"std::option::Option::unwrap",
"std::option::Option::expect",
]
Expand Down
3 changes: 3 additions & 0 deletions mqtt-format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@
#![deny(clippy::disallowed_methods)]
#![deny(clippy::disallowed_types)]

#[cfg(feature = "mqttv3")]
pub mod v3;
#[cfg(feature = "mqttv5")]
pub mod v5;
30 changes: 30 additions & 0 deletions mqtt-format/src/v5/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use winnow::{binary::length_take, Bytes, Parser};

use super::MResult;

pub fn parse_binary_data<'i>(input: &mut &'i Bytes) -> MResult<&'i [u8]> {
length_take(super::integers::parse_u16).parse_next(input)
}

#[cfg(test)]
mod tests {
use winnow::Bytes;

use crate::v5::bytes::parse_binary_data;

#[test]
fn check_binary_data() {
let input = &[0x0, 0x2, 0x4, 0x2];

assert_eq!(
parse_binary_data(&mut Bytes::new(input)).unwrap(),
&[0x4, 0x2]
);
}
}
124 changes: 124 additions & 0 deletions mqtt-format/src/v5/fixed_header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use winnow::{
binary::bits::bits,
error::{ErrMode, FromExternalError, InputError, ParserError},
Bytes, Parser,
};

use super::MResult;

#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum QualityOfService {
AtMostOnce = 0,
AtLeastOnce = 1,
ExactlyOnce = 2,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PacketType {
Connect,
Connack,
Publish {
dup: bool,
qos: QualityOfService,
retain: bool,
},
Puback,
Pubrec,
Pubrel,
Pubcomp,
Subscribe,
Suback,
Unsubscribe,
Unsuback,
Pingreq,
Pingresp,
Disconnect,
Auth,
}

#[derive(Debug, PartialEq)]
pub struct MFixedHeader {
pub packet_type: PacketType,
}

impl MFixedHeader {
pub fn parse(input: &mut &Bytes) -> MResult<MFixedHeader> {
let (packet_type, packet_flags): (u8, u8) = bits::<_, _, InputError<(_, usize)>, _, _>((
winnow::binary::bits::take(4usize),
winnow::binary::bits::take(4usize),
))
.parse_next(input)
.map_err(|_: ErrMode<InputError<_>>| {
ErrMode::from_error_kind(input, winnow::error::ErrorKind::Slice)
})?;

let packet_type = match (packet_type, packet_flags) {
(0, _) => {
return Err(ErrMode::from_error_kind(
input,
winnow::error::ErrorKind::Verify,
))
}
(1, 0) => PacketType::Connect,
(2, 0) => PacketType::Connack,
(3, flags) => PacketType::Publish {
dup: (0b1000 & flags) != 0,
qos: QualityOfService::try_from((flags & 0b0110) >> 1).map_err(|e| {
ErrMode::from_external_error(input, winnow::error::ErrorKind::Verify, e)
})?,
retain: (0b0001 & flags) != 0,
},
(4, 0) => PacketType::Puback,
(5, 0) => PacketType::Pubrec,
(6, 0b0010) => PacketType::Pubrel,
(7, 0) => PacketType::Pubcomp,
(8, 0b0010) => PacketType::Subscribe,
(9, 0) => PacketType::Suback,
(10, 0b0010) => PacketType::Unsubscribe,
(11, 0) => PacketType::Unsuback,
(12, 0) => PacketType::Pingreq,
(13, 0) => PacketType::Pingresp,
(14, 0) => PacketType::Disconnect,
(15, 0) => PacketType::Auth,
_ => {
return Err(ErrMode::from_error_kind(
input,
winnow::error::ErrorKind::Verify,
))
}
};

Ok(MFixedHeader { packet_type })
}
}

#[cfg(test)]
mod tests {
use winnow::Bytes;

use crate::v5::fixed_header::MFixedHeader;

#[test]
fn check_fixed_header() {
let input = &[0b0011_1010];

assert_eq!(
MFixedHeader::parse(&mut Bytes::new(&input)).unwrap(),
MFixedHeader {
packet_type: crate::v5::fixed_header::PacketType::Publish {
dup: true,
qos: crate::v5::fixed_header::QualityOfService::AtLeastOnce,
retain: false
},
}
)
}
}
99 changes: 99 additions & 0 deletions mqtt-format/src/v5/integers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use winnow::{combinator::trace, token::take_while, Bytes, Parser};

use super::MResult;

pub fn parse_u16(input: &mut &Bytes) -> MResult<u16> {
trace(
"parse_u16",
winnow::binary::u16(winnow::binary::Endianness::Big),
)
.parse_next(input)
}

pub fn parse_u32(input: &mut &Bytes) -> MResult<u32> {
trace(
"parse_u32",
winnow::binary::u32(winnow::binary::Endianness::Big),
)
.parse_next(input)
}

pub fn parse_variable_u32(input: &mut &Bytes) -> MResult<u32> {
trace("parse_variable_u32", |input: &mut &Bytes| {
let var_bytes = (
take_while(0..=3, |b| b & 0b1000_0000 != 0),
winnow::binary::u8.verify(|b: &u8| b & 0b1000_0000 == 0),
);
let bytes: &[u8] = var_bytes.recognize().parse_next(input)?;

let mut output: u32 = 0;

for (exp, val) in bytes.iter().enumerate() {
output += (*val as u32 & 0b0111_1111) * 128u32.pow(exp as u32);
}

Ok(output)
})
.parse_next(input)
}

#[cfg(test)]
mod tests {
use winnow::Bytes;

use crate::v5::integers::{parse_u16, parse_u32, parse_variable_u32};

#[test]
fn check_integer_parsing() {
let input = 15u16.to_be_bytes();
assert_eq!(parse_u16(&mut Bytes::new(&input)).unwrap(), 15);

let input = 42u32.to_be_bytes();
assert_eq!(parse_u32(&mut Bytes::new(&input)).unwrap(), 42);
}

#[test]
fn check_variable_integers() {
let input = [0x0];
assert_eq!(parse_variable_u32(&mut Bytes::new(&input)).unwrap(), 0);

let input = [0x7F];
assert_eq!(parse_variable_u32(&mut Bytes::new(&input)).unwrap(), 127);

let input = [0x80, 0x01];
assert_eq!(parse_variable_u32(&mut Bytes::new(&input)).unwrap(), 128);

let input = [0xFF, 0x7F];
assert_eq!(parse_variable_u32(&mut Bytes::new(&input)).unwrap(), 16_383);

let input = [0x80, 0x80, 0x01];
assert_eq!(parse_variable_u32(&mut Bytes::new(&input)).unwrap(), 16_384);

let input = [0xFF, 0xFF, 0x7F];
assert_eq!(
parse_variable_u32(&mut Bytes::new(&input)).unwrap(),
2_097_151
);

let input = [0x80, 0x80, 0x80, 0x01];
assert_eq!(
parse_variable_u32(&mut Bytes::new(&input)).unwrap(),
2_097_152
);

let input = [0xFF, 0xFF, 0xFF, 0x7F];
assert_eq!(
parse_variable_u32(&mut Bytes::new(&input)).unwrap(),
268_435_455
);

let input = [0xFF, 0xFF, 0xFF, 0x8F];
parse_variable_u32(&mut Bytes::new(&input)).unwrap_err();
}
}
29 changes: 29 additions & 0 deletions mqtt-format/src/v5/level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use winnow::error::{ErrMode, ParserError};
use winnow::Bytes;

use super::MResult;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ProtocolLevel {
V3,
V5,
}

impl ProtocolLevel {
pub fn parse(input: &mut &Bytes) -> MResult<Self> {
match winnow::binary::u8(input)? {
3 => Ok(Self::V3),
5 => Ok(Self::V5),
_ => Err(ErrMode::from_error_kind(
input,
winnow::error::ErrorKind::Verify,
)),
}
}
}
20 changes: 20 additions & 0 deletions mqtt-format/src/v5/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

#![deny(missing_debug_implementations)]

pub mod bytes;
pub mod fixed_header;
pub mod integers;
pub mod level;
pub mod packets;
pub mod properties;
pub mod reason_code;
pub mod strings;
pub mod util;
pub mod variable_header;

pub type MResult<O> = winnow::PResult<O>;
Loading

0 comments on commit a068335

Please sign in to comment.