blob: ae33d8c2c7de5a5064653442a364f09f0a2173dd [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Defines the primitive parsers that the rest of the mojom parsers build upon.
//!
//! This module contains the lowest-level parsers. These parse simple, generic
//! data types (ints and floats), and interact directly with the encoded data as
//! a byte stream.
//!
//! In this library, a *parser* is a function that takes in a byte array (and
//! possibly some other information) and returns a `Result<value>`, for some
//! type `value`. In the process, it mutates the input data stream, dropping
//! however much data it read.
//!
//! Since Mojom messages encode expected offsets of data for validation
//! purposes, parsers also track how much data has been parsed in total.
//!
//! All parsers ensure that enough data exists, and return an error result if
//! not.
// FOR_RELEASE: This strategy basically re-invents nom, so we should consider
// switching to that if we intend to keep going down this route.
// FOR_RELEASE: The original doc says everything is little-endian, but someone
// told me it might all be host-endian. Figure that out before it causes
// problems.
use crate::errors::*;
use std::any::type_name;
/// The input to a parser
pub struct ParserData<'a> {
remaining_bytes: &'a [u8],
bytes_parsed: usize,
}
// Since the primitive parsers require mutable references, encapsulate the
// internal representation so we don't accidentally mutate it elsewhere.
impl<'a> ParserData<'a> {
/// Create a new ParserData from a byte array.
pub fn new(data: &'a [u8]) -> ParserData<'a> {
ParserData { remaining_bytes: data, bytes_parsed: 0 }
}
/// How many bytes have been parsed since the ParserData was created.
pub fn bytes_parsed(&self) -> usize {
self.bytes_parsed
}
/// How many bytes remain to be parsed.
pub fn remaining_bytes(&self) -> usize {
self.remaining_bytes.len()
}
}
/// Skips the next `bytes_to_parse` bytes, assuming they exist.
pub fn parse_padding(data: &mut ParserData, bytes_to_parse: usize) -> ParsingResult<()> {
let mk_err = || ParsingError {
offset: data.bytes_parsed(),
ty: ParsingErrorType::NotEnoughData {
tried_to_parse: format!("padding"),
expected_size: bytes_to_parse,
remaining_bytes: data.remaining_bytes.len(),
},
};
data.remaining_bytes =
data.remaining_bytes.get((bytes_to_parse as usize)..).ok_or_else(mk_err)?;
data.bytes_parsed += bytes_to_parse;
Ok(())
}
// Declares a function named $name, which takes a byte slice (&[u8]), reads the
// first $size_in_bytes entries, and interprets them as a value of type
// $target_type, assuming they are in little-endian order.
// Returns the parsed value, and the number of bytes parsed, and
// the remainder of the byte slice.
// Returns an error if there aren't enough bytes in the slice.
macro_rules! declare_primitive_parser {
($target_type:ty, $size_in_bytes:literal, $name:ident) => {
pub fn $name(data: &mut ParserData) -> ParsingResult<$target_type> {
let mk_err = || ParsingError {
offset: data.bytes_parsed(),
ty: ParsingErrorType::NotEnoughData {
tried_to_parse: type_name::<$target_type>().to_string(),
expected_size: $size_in_bytes,
remaining_bytes: data.remaining_bytes.len(),
},
};
let (head, tail) =
data.remaining_bytes.split_first_chunk::<$size_in_bytes>().ok_or_else(mk_err)?;
let ret = <$target_type>::from_le_bytes(*head);
data.remaining_bytes = tail;
data.bytes_parsed += $size_in_bytes;
Ok(ret)
}
};
}
declare_primitive_parser!(u8, 1, parse_u8);
declare_primitive_parser!(u16, 2, parse_u16);
declare_primitive_parser!(u32, 4, parse_u32);
declare_primitive_parser!(u64, 8, parse_u64);
declare_primitive_parser!(i8, 1, parse_i8);
declare_primitive_parser!(i16, 2, parse_i16);
declare_primitive_parser!(i32, 4, parse_i32);
declare_primitive_parser!(i64, 8, parse_i64);
declare_primitive_parser!(f32, 4, parse_f32);
declare_primitive_parser!(f64, 8, parse_f64);