Skip to content

Commit

Permalink
Use a custom error type
Browse files Browse the repository at this point in the history
I've got a PR open for Winnow (winnow-rs/winnow#500) that should
hopefully render this unnecessary once it's merged & released, but until
then this lets me use context with bit parsers (and simplifies
invocation for `bits::bits`!)
  • Loading branch information
Lexicality committed Apr 7, 2024
1 parent 8caab69 commit 2d068b2
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 61 deletions.
10 changes: 3 additions & 7 deletions src/bin/test_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use libmbus::parse::link_layer::Packet;
use libmbus::parse::transport_layer::CICode;
use std::error;
use winnow::binary::bits;
use winnow::error::InputError;
use winnow::{Bytes, Parser};

fn do_file(fname: &str) -> Result<(), Box<dyn error::Error>> {
Expand All @@ -25,12 +24,9 @@ fn do_file(fname: &str) -> Result<(), Box<dyn error::Error>> {
println!("{ci:#?}");

// Parse the first DIB & VIB
let (dib, vib) = bits::bits::<_, _, _, InputError<_>, _>((
DataInfoBlock::parse,
ValueInfoBlock::parse,
))
.parse_next(&mut data)
.map_err(|e| e.to_string())?;
let (dib, vib) = bits::bits((DataInfoBlock::parse, ValueInfoBlock::parse))
.parse_next(&mut data)
.map_err(|e| e.to_string())?;
println!("{dib:?}\n{vib:?}");
}
_ => todo!(),
Expand Down
9 changes: 5 additions & 4 deletions src/parse/application_layer/dib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the EUPL-1.2
#![allow(dead_code)]

use crate::parse::types::{BResult, BitsInput};
use crate::parse::error::MBResult;
use crate::parse::types::BitsInput;
use winnow::binary::bits;
use winnow::error::{ErrMode, ParserError};
use winnow::Parser;
Expand All @@ -17,7 +18,7 @@ pub enum RawDataType {
}

impl RawDataType {
fn parse<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Self> {
fn parse(input: &mut BitsInput<'_>) -> MBResult<Self> {
bits::take(4_usize)
.verify_map(|value: u8| match value {
0b0000 => Some(Self::None),
Expand All @@ -44,7 +45,7 @@ pub enum DataFunction {
}

impl DataFunction {
fn parse<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Self> {
fn parse(input: &mut BitsInput<'_>) -> MBResult<Self> {
bits::take(2_usize)
.map(|value: u8| match value {
0b00 => Self::InstantaneousValue,
Expand All @@ -67,7 +68,7 @@ pub struct DataInfoBlock {
}

impl DataInfoBlock {
pub fn parse<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Self> {
pub fn parse(input: &mut BitsInput<'_>) -> MBResult<Self> {
let (mut extension, mut storage, function, raw_type): (bool, u64, _, _) = (
bits::bool,
bits::take(1_usize),
Expand Down
18 changes: 9 additions & 9 deletions src/parse/application_layer/vib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// Licensed under the EUPL-1.2
#![allow(dead_code)]

use crate::parse::error::MBResult;
use crate::parse::types::string::parse_length_prefix_ascii;
use crate::parse::types::{BResult, BitsInput};
use crate::parse::types::BitsInput;
use winnow::binary::bits;
use winnow::error::{ErrMode, ErrorKind, InputError, ParserError};
use winnow::error::{ErrMode, ErrorKind, ParserError};
use winnow::prelude::*;

const VIF_EXTENSION_1: u8 = 0b0111_1011;
Expand All @@ -25,11 +26,11 @@ pub struct ValueInfoBlock {
extra_vifes: Option<Vec<u8>>,
}

pub fn parse_vif_byte<'a>(input: &mut BitsInput<'a>) -> BResult<'a, (bool, u8)> {
pub fn parse_vif_byte(input: &mut BitsInput<'_>) -> MBResult<(bool, u8)> {
(bits::bool, bits::take(7_usize)).parse_next(input)
}

pub fn dump_remaining_vifes<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Vec<u8>> {
pub fn dump_remaining_vifes(input: &mut BitsInput<'_>) -> MBResult<Vec<u8>> {
let mut ret = Vec::new();
loop {
let (extension, value) = parse_vif_byte.parse_next(input)?;
Expand All @@ -42,7 +43,7 @@ pub fn dump_remaining_vifes<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Vec<u8
}

impl ValueInfoBlock {
pub fn parse<'a>(input: &mut BitsInput<'a>) -> BResult<'a, Self> {
pub fn parse(input: &mut BitsInput<'_>) -> MBResult<Self> {
let (mut extension, raw_value) = parse_vif_byte.parse_next(input)?;

let value_type = match raw_value {
Expand Down Expand Up @@ -85,10 +86,9 @@ impl ValueInfoBlock {

// Now we've parsed all the VIFEs we can get the ascii VIF if necessary
let value_type = match value_type {
ValueType::PlainText(_) => ValueType::PlainText(
bits::bytes::<_, _, InputError<_>, _, _>(parse_length_prefix_ascii)
.parse_next(input)?,
),
ValueType::PlainText(_) => {
ValueType::PlainText(bits::bytes(parse_length_prefix_ascii).parse_next(input)?)
}
value_type => value_type,
};

Expand Down
74 changes: 74 additions & 0 deletions src/parse/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
// Licensed under the EUPL-1.2

use std::{error, fmt};
use winnow::error::{
AddContext, ContextError, ErrorConvert, ErrorKind, FromExternalError, InputError, ParserError,
StrContext,
};
use winnow::stream::Stream;
use winnow::PResult;

#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
Expand Down Expand Up @@ -39,3 +45,71 @@ impl fmt::Display for ParseError {
impl error::Error for ParseError {}

pub type Result<T> = std::result::Result<T, ParseError>;

/// Because the version of Winnow we're using doesn't let you use `ContextError`
/// with the bit-level parsers I've had to wrap it in a struct I control so I
/// can implement `ErrorConvert` and get it working again
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MBusError(ContextError<StrContext>);

pub type MBResult<O> = PResult<O, MBusError>;

impl MBusError {
pub fn new() -> Self {
Self(ContextError::new())
}

pub fn context(&self) -> impl Iterator<Item = &StrContext> {
self.0.context()
}

pub fn cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
self.0.cause()
}
}

impl<I: Stream> ParserError<I> for MBusError {
fn append(self, input: &I, token_start: &<I as Stream>::Checkpoint, kind: ErrorKind) -> Self {
Self(self.0.append(input, token_start, kind))
}

fn from_error_kind(input: &I, kind: ErrorKind) -> Self {
Self(ContextError::from_error_kind(input, kind))
}
}

impl std::fmt::Display for MBusError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl<I: Stream> AddContext<I, StrContext> for MBusError {
fn add_context(
self,
input: &I,
token_start: &<I as Stream>::Checkpoint,
context: StrContext,
) -> Self {
Self(self.0.add_context(input, token_start, context))
}
}

impl<I, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E> for MBusError {
fn from_external_error(input: &I, kind: ErrorKind, e: E) -> Self {
Self(ContextError::from_external_error(input, kind, e))
}
}

impl ErrorConvert<MBusError> for MBusError {
fn convert(self) -> MBusError {
self
}
}

// impl<I: Stream> ErrorConvert<InputError<I>> for MBusError {
impl<I: Stream + Clone> ErrorConvert<MBusError> for InputError<I> {
fn convert(self) -> MBusError {
MBusError::from_error_kind(&self.input, self.kind)
}
}
10 changes: 6 additions & 4 deletions src/parse/link_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use winnow::prelude::*;
use winnow::stream::Stream;
use winnow::Bytes;

use super::error::MBResult;

const LONG_FRAME_HEADER: u8 = 0x68;
const SHORT_FRAME_HEADER: u8 = 0x10;
const FRAME_TAIL: u8 = 0x16;
Expand All @@ -27,7 +29,7 @@ pub enum Packet<'a> {
},
}

fn parse_variable<'a>(input: &mut &'a Bytes) -> PResult<Packet<'a>> {
fn parse_variable<'a>(input: &mut &'a Bytes) -> MBResult<Packet<'a>> {
let length = binary::u8
.context(StrContext::Label("length"))
.parse_next(input)?;
Expand Down Expand Up @@ -88,7 +90,7 @@ fn parse_variable<'a>(input: &mut &'a Bytes) -> PResult<Packet<'a>> {
})
}

fn parse_fixed<'a>(input: &mut &'a Bytes) -> PResult<Packet<'a>> {
fn parse_fixed<'a>(input: &mut &'a Bytes) -> MBResult<Packet<'a>> {
// mbus's fixed length datagrams are 2 bytes long, only control & address
let (control, address, checksum, _) = (
binary::u8.context(StrContext::Label("control byte")),
Expand All @@ -112,12 +114,12 @@ fn parse_fixed<'a>(input: &mut &'a Bytes) -> PResult<Packet<'a>> {
Ok(Packet::Short { control, address })
}

fn parse_ack<'a>(_input: &mut &'a Bytes) -> PResult<Packet<'a>> {
fn parse_ack<'a>(_input: &mut &'a Bytes) -> MBResult<Packet<'a>> {
Ok(Packet::Ack)
}

impl<'a> Packet<'a> {
pub fn parse(input: &mut &'a Bytes) -> PResult<Packet<'a>> {
pub fn parse(input: &mut &'a Bytes) -> MBResult<Packet<'a>> {
alt((
preceded(
LONG_FRAME_HEADER.void(),
Expand Down
4 changes: 3 additions & 1 deletion src/parse/transport_layer/control_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use winnow::error::StrContext;
use winnow::prelude::*;
use winnow::Bytes;

use crate::parse::error::MBResult;

use super::header::LongHeader;
use super::header::TPLHeader;

Expand Down Expand Up @@ -47,7 +49,7 @@ pub enum CICode {
}

impl CICode {
pub fn parse(input: &mut &Bytes) -> PResult<CICode> {
pub fn parse(input: &mut &Bytes) -> MBResult<CICode> {
let ci = binary::u8
.context(StrContext::Label("CI field"))
.parse_next(input)?;
Expand Down
16 changes: 7 additions & 9 deletions src/parse/transport_layer/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// Licensed under the EUPL-1.2
#![allow(dead_code)]
use winnow::binary;
use winnow::error::{ContextError, InputError, ParserError, StrContext};
use winnow::error::{InputError, StrContext};
use winnow::prelude::*;
use winnow::Bytes;

use crate::parse::error::MBResult;
use crate::parse::types::number::parse_bcd;

use super::manufacturer::{device_name, unpack_manufacturer_code};
Expand Down Expand Up @@ -50,7 +51,7 @@ pub struct MeterStatus {
}

impl MeterStatus {
fn parse(input: &mut &Bytes) -> PResult<MeterStatus> {
fn parse(input: &mut &Bytes) -> MBResult<MeterStatus> {
binary::bits::bits::<_, _, InputError<_>, _, _>((
binary::bits::bool,
binary::bits::bool,
Expand Down Expand Up @@ -86,9 +87,6 @@ impl MeterStatus {
},
)
.parse_next(input)
.map_err(|err| {
err.map(|err: InputError<_>| ContextError::from_error_kind(&err.input, err.kind))
})
}
}

Expand All @@ -101,11 +99,11 @@ pub struct ShortHeader {
}

impl ShortHeader {
pub fn parse(input: &mut &Bytes) -> PResult<TPLHeader> {
pub fn parse(input: &mut &Bytes) -> MBResult<TPLHeader> {
Self::parse_raw.map(TPLHeader::Short).parse_next(input)
}

fn parse_raw(input: &mut &Bytes) -> PResult<ShortHeader> {
fn parse_raw(input: &mut &Bytes) -> MBResult<ShortHeader> {
(
binary::u8.context(StrContext::Label("access number")),
MeterStatus::parse.context(StrContext::Label("status")),
Expand Down Expand Up @@ -194,7 +192,7 @@ pub enum DeviceType {
}

impl DeviceType {
fn parse(input: &mut &Bytes) -> PResult<DeviceType> {
fn parse(input: &mut &Bytes) -> MBResult<DeviceType> {
binary::u8
.map(|v| match v {
0x00 => DeviceType::Other,
Expand Down Expand Up @@ -228,7 +226,7 @@ pub struct LongHeader {
}

impl LongHeader {
pub fn parse(input: &mut &Bytes) -> PResult<TPLHeader> {
pub fn parse(input: &mut &Bytes) -> MBResult<TPLHeader> {
(
parse_bcd(4)
.context(StrContext::Label("device identifier"))
Expand Down
5 changes: 1 addition & 4 deletions src/parse/types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Copyright 2023 Lexi Robinson
// Licensed under the EUPL-1.2

use winnow::error::InputError;
use winnow::{Bytes, PResult};
use winnow::Bytes;

use super::error::Result;

Expand Down Expand Up @@ -32,5 +31,3 @@ pub enum DataType {
pub type ParseResult = Result<DataType>;

pub type BitsInput<'a> = (&'a Bytes, usize);
pub type BitsError<'a> = InputError<BitsInput<'a>>;
pub type BResult<'a, O> = PResult<O, BitsError<'a>>;
20 changes: 7 additions & 13 deletions src/parse/types/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

use winnow::binary;
use winnow::combinator::repeat;
use winnow::error::{ContextError, ErrMode, InputError, ParserError};
use winnow::error::{ErrMode, ParserError};
use winnow::prelude::*;
use winnow::Bytes;

use crate::parse::application_layer::dib::RawDataType;
use crate::parse::application_layer::vib::ValueType;
use crate::parse::error::{ParseError, Result};
use crate::parse::error::{MBResult, MBusError, ParseError, Result};
use crate::parse::Datagram;

use super::{BResult, BitsInput, DataType, ParseResult};
use super::{BitsInput, DataType, ParseResult};

pub fn parse_number(dt: RawDataType, vt: ValueType, dg: &mut Datagram) -> ParseResult {
match dt {
Expand All @@ -31,15 +31,15 @@ pub fn parse_number(dt: RawDataType, vt: ValueType, dg: &mut Datagram) -> ParseR
}
}

fn parse_nibble<'a>(input: &mut BitsInput<'a>) -> BResult<'a, i64> {
fn parse_nibble(input: &mut BitsInput<'_>) -> MBResult<i64> {
binary::bits::take(4_usize).parse_next(input)
}

fn parse_bcd_nibble<'a>(input: &mut BitsInput<'a>) -> BResult<'a, i64> {
fn parse_bcd_nibble(input: &mut BitsInput<'_>) -> MBResult<i64> {
parse_nibble.verify(|v| *v < 10).parse_next(input)
}

pub fn parse_bcd<'a>(bytes: usize) -> impl Parser<&'a Bytes, i64, ContextError> {
pub fn parse_bcd<'a>(bytes: usize) -> impl Parser<&'a Bytes, i64, MBusError> {
let parser = move |input: &mut BitsInput<'a>| {
if bytes == 0 {
return Err(ErrMode::assert(input, "cannot parse 0 bytes"));
Expand Down Expand Up @@ -72,13 +72,7 @@ pub fn parse_bcd<'a>(bytes: usize) -> impl Parser<&'a Bytes, i64, ContextError>
Ok(if neg { -result } else { result })
};

move |input: &mut &'a Bytes| {
binary::bits::bits::<_, _, InputError<_>, _, _>(parser)
.parse_next(input)
.map_err(|err| {
err.map(|err: InputError<_>| ContextError::from_error_kind(&err.input, err.kind))
})
}
move |input: &mut &'a Bytes| binary::bits::bits(parser).parse_next(input)
}

fn decode_bcd(mut data: Vec<u8>) -> ParseResult {
Expand Down
Loading

0 comments on commit 2d068b2

Please sign in to comment.