From 6b4269710e62bc13735a18709aecd34d3960a576 Mon Sep 17 00:00:00 2001 From: rmsyn Date: Sat, 28 Jan 2023 20:39:21 +0000 Subject: [PATCH] lib: enable no-std compatibility Add opt-in `no_std` feature, and use compatible data structures from `alloc`, `core`, and `core2` for `no-std` builds. To build for `no-std` pass `--features no_std` on the command line, or `features = ["no_std"]` in a Cargo.toml using `lzma-rs`. --- Cargo.toml | 2 ++ src/decode/lzbuffer.rs | 3 +++ src/decode/lzma.rs | 11 ++++++++--- src/decode/lzma2.rs | 6 ++++-- src/decode/rangecoder.rs | 3 +++ src/decode/stream.rs | 20 ++++++++++++++++---- src/decode/util.rs | 3 +++ src/decode/xz.rs | 8 ++++++-- src/encode/dumbencoder.rs | 3 +++ src/encode/lzma2.rs | 5 +++++ src/encode/rangecoder.rs | 5 +++++ src/encode/util.rs | 3 +++ src/encode/xz.rs | 6 ++++-- src/error.rs | 30 ++++++++++++++++++++---------- src/lib.rs | 7 +++++++ src/util/vec2d.rs | 5 +++++ src/xz/header.rs | 6 +++++- src/xz/mod.rs | 16 +++++++++++++--- 18 files changed, 115 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e986103..3d28c47b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ byteorder = "1.4.3" crc = "3.0.0" log = { version = "0.4.17", optional = true } env_logger = { version = "0.9.0", optional = true } +core2 = { version = "^0.4", optional = true } [dev-dependencies] rust-lzma = "0.5" @@ -25,6 +26,7 @@ seq-macro = "0.3" enable_logging = ["env_logger", "log"] stream = [] raw_decoder = [] +no_std = ["core2"] [package.metadata.docs.rs] features = ["stream", "raw_decoder"] diff --git a/src/decode/lzbuffer.rs b/src/decode/lzbuffer.rs index 9499d968..c26ca880 100644 --- a/src/decode/lzbuffer.rs +++ b/src/decode/lzbuffer.rs @@ -1,4 +1,7 @@ use crate::error; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub trait LzBuffer diff --git a/src/decode/lzma.rs b/src/decode/lzma.rs index bbb98e18..9d2ca8be 100644 --- a/src/decode/lzma.rs +++ b/src/decode/lzma.rs @@ -3,7 +3,12 @@ use crate::decode::rangecoder::{BitTree, LenDecoder, RangeDecoder}; use crate::decompress::{Options, UnpackedSize}; use crate::error; use crate::util::vec2d::Vec2D; +#[cfg(feature = "no_std")] +use alloc::string::String; use byteorder::{LittleEndian, ReadBytesExt}; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; /// Maximum input data that can be processed in one iteration. @@ -165,7 +170,7 @@ impl LzmaParams { pub(crate) struct DecoderState { // Buffer input data here if we need more for decompression. Up to // MAX_REQUIRED_INPUT bytes can be consumed during one iteration. - partial_input_buf: std::io::Cursor<[u8; MAX_REQUIRED_INPUT]>, + partial_input_buf: io::Cursor<[u8; MAX_REQUIRED_INPUT]>, pub(crate) lzma_props: LzmaProperties, unpacked_size: Option, literal_probs: Vec2D, @@ -188,7 +193,7 @@ impl DecoderState { pub fn new(lzma_props: LzmaProperties, unpacked_size: Option) -> Self { lzma_props.validate(); DecoderState { - partial_input_buf: std::io::Cursor::new([0; MAX_REQUIRED_INPUT]), + partial_input_buf: io::Cursor::new([0; MAX_REQUIRED_INPUT]), lzma_props, unpacked_size, literal_probs: Vec2D::init(0x400, (1 << (lzma_props.lc + lzma_props.lp), 0x300)), @@ -412,7 +417,7 @@ impl DecoderState { range: u32, code: u32, ) -> error::Result<()> { - let mut temp = std::io::Cursor::new(buf); + let mut temp = io::Cursor::new(buf); let mut rangecoder = RangeDecoder::from_parts(&mut temp, range, code); let _ = self.process_next_inner(output, &mut rangecoder, false)?; Ok(()) diff --git a/src/decode/lzma2.rs b/src/decode/lzma2.rs index 24f75186..7ca0eec9 100644 --- a/src/decode/lzma2.rs +++ b/src/decode/lzma2.rs @@ -3,8 +3,10 @@ use crate::decode::lzma::{DecoderState, LzmaProperties}; use crate::decode::{lzbuffer, rangecoder}; use crate::error; use byteorder::{BigEndian, ReadBytesExt}; -use std::io; -use std::io::Read; +#[cfg(feature = "no_std")] +use core2::io::{self, Read}; +#[cfg(not(feature = "no_std"))] +use std::io::{self, Read}; #[derive(Debug)] /// Raw decoder for LZMA2. diff --git a/src/decode/rangecoder.rs b/src/decode/rangecoder.rs index 065d70bb..b95f6119 100644 --- a/src/decode/rangecoder.rs +++ b/src/decode/rangecoder.rs @@ -2,6 +2,9 @@ use crate::decode::util; use crate::error; use crate::util::const_assert; use byteorder::{BigEndian, ReadBytesExt}; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub struct RangeDecoder<'a, R> diff --git a/src/decode/stream.rs b/src/decode/stream.rs index a570f94e..c64e14d2 100644 --- a/src/decode/stream.rs +++ b/src/decode/stream.rs @@ -3,8 +3,18 @@ use crate::decode::lzma::{DecoderState, LzmaParams}; use crate::decode::rangecoder::RangeDecoder; use crate::decompress::Options; use crate::error::Error; -use std::fmt::Debug; +#[cfg(feature = "no_std")] +use core::fmt::{self, Debug}; +#[cfg(feature = "no_std")] +use core::u64::MAX as U64_MAX; +#[cfg(feature = "no_std")] +use core2::io::{self, BufRead, Cursor, Read, Write}; +#[cfg(not(feature = "no_std"))] +use std::fmt::{self, Debug}; +#[cfg(not(feature = "no_std"))] use std::io::{self, BufRead, Cursor, Read, Write}; +#[cfg(not(feature = "no_std"))] +use std::u64::MAX as U64_MAX; /// Minimum header length to be read. /// - props: u8 (1 byte) @@ -52,7 +62,7 @@ impl Debug for RunState where W: Write, { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("RunState") .field("range", &self.range) .field("code", &self.code) @@ -211,7 +221,7 @@ impl Debug for Stream where W: Write + Debug, { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("Stream") .field("tmp", &self.tmp.position()) .field("state", &self.state) @@ -277,7 +287,7 @@ where // reset the cursor because we may have partial reads input.set_position(0); let bytes_read = input.read(&mut self.tmp.get_mut()[..])?; - let bytes_read = if bytes_read < std::u64::MAX as usize { + let bytes_read = if bytes_read < U64_MAX as usize { bytes_read as u64 } else { return Err(io::Error::new( @@ -348,6 +358,8 @@ impl From for io::Error { #[cfg(test)] mod test { use super::*; + #[cfg(feature = "no_std")] + use alloc::vec::Vec; /// Test an empty stream #[test] diff --git a/src/decode/util.rs b/src/decode/util.rs index 32f1dfe6..70f377ce 100644 --- a/src/decode/util.rs +++ b/src/decode/util.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub fn read_tag(input: &mut R, tag: &[u8]) -> io::Result { diff --git a/src/decode/xz.rs b/src/decode/xz.rs index 95aeca06..dbf1f916 100644 --- a/src/decode/xz.rs +++ b/src/decode/xz.rs @@ -5,9 +5,13 @@ use crate::decode::util; use crate::error; use crate::xz::crc::{CRC32, CRC64}; use crate::xz::{footer, header, CheckMethod, StreamFlags}; +#[cfg(feature = "no_std")] +use alloc::vec::Vec; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; -use std::io; -use std::io::Read; +#[cfg(feature = "no_std")] +use core2::io::{self, Read}; +#[cfg(not(feature = "no_std"))] +use std::io::{self, Read}; #[derive(Debug)] struct Record { diff --git a/src/encode/dumbencoder.rs b/src/encode/dumbencoder.rs index f1574c50..dad44fb6 100644 --- a/src/encode/dumbencoder.rs +++ b/src/encode/dumbencoder.rs @@ -1,6 +1,9 @@ use crate::compress::{Options, UnpackedSize}; use crate::encode::rangecoder; use byteorder::{LittleEndian, WriteBytesExt}; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub struct Encoder<'a, W> diff --git a/src/encode/lzma2.rs b/src/encode/lzma2.rs index ead07260..a7e34dd9 100644 --- a/src/encode/lzma2.rs +++ b/src/encode/lzma2.rs @@ -1,4 +1,9 @@ +#[cfg(feature = "no_std")] +use alloc::vec; use byteorder::{BigEndian, WriteBytesExt}; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub fn encode_stream(input: &mut R, output: &mut W) -> io::Result<()> diff --git a/src/encode/rangecoder.rs b/src/encode/rangecoder.rs index fea87389..76bbc8ce 100644 --- a/src/encode/rangecoder.rs +++ b/src/encode/rangecoder.rs @@ -1,4 +1,9 @@ +#[cfg(all(test, feature = "no_std"))] +use alloc::vec::Vec; use byteorder::WriteBytesExt; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; #[cfg(test)] diff --git a/src/encode/util.rs b/src/encode/util.rs index e95b2242..1eaece87 100644 --- a/src/encode/util.rs +++ b/src/encode/util.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; /// An [`io::Write`] computing a digest on the bytes written. diff --git a/src/encode/xz.rs b/src/encode/xz.rs index 42bf9ef9..d3e75036 100644 --- a/src/encode/xz.rs +++ b/src/encode/xz.rs @@ -3,8 +3,10 @@ use crate::encode::{lzma2, util}; use crate::xz::crc::CRC32; use crate::xz::{footer, header, CheckMethod, StreamFlags}; use byteorder::{LittleEndian, WriteBytesExt}; -use std::io; -use std::io::Write; +#[cfg(feature = "no_std")] +use core2::io::{self, Write}; +#[cfg(not(feature = "no_std"))] +use std::io::{self, Write}; pub fn encode_stream(input: &mut R, output: &mut W) -> io::Result<()> where diff --git a/src/error.rs b/src/error.rs index 2ee0fbe5..b2043cad 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,17 @@ //! Error handling. -use std::fmt::Display; -use std::{io, result}; +#[cfg(feature = "no_std")] +use alloc::string::String; +#[cfg(feature = "no_std")] +use core::fmt::{self, Display}; +#[cfg(feature = "no_std")] +use core::result; +#[cfg(feature = "no_std")] +use core2::{error, io}; +#[cfg(not(feature = "no_std"))] +use std::fmt::{self, Display}; +#[cfg(not(feature = "no_std"))] +use std::{error, io, result}; /// Library errors. #[derive(Debug)] @@ -26,7 +36,7 @@ impl From for Error { } impl Display for Error { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::IoError(e) => write!(fmt, "io error: {}", e), Error::HeaderTooShort(e) => write!(fmt, "header too short: {}", e), @@ -36,8 +46,8 @@ impl Display for Error { } } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::IoError(e) | Error::HeaderTooShort(e) => Some(e), Error::LzmaError(_) | Error::XzError(_) => None, @@ -48,15 +58,15 @@ impl std::error::Error for Error { #[cfg(test)] mod test { use super::Error; + #[cfg(feature = "no_std")] + use core2::io; + #[cfg(not(feature = "no_std"))] + use std::io; #[test] fn test_display() { assert_eq!( - Error::IoError(std::io::Error::new( - std::io::ErrorKind::Other, - "this is an error" - )) - .to_string(), + Error::IoError(io::Error::new(io::ErrorKind::Other, "this is an error")).to_string(), "io error: this is an error" ); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index deb849e1..ccde7de2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ //! Pure-Rust codecs for LZMA, LZMA2, and XZ. #![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))] +#![cfg_attr(no_std, feature(no_std))] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![forbid(unsafe_code)] +#[cfg(feature = "no_std")] +extern crate alloc; + #[macro_use] mod macros; @@ -15,6 +19,9 @@ pub mod error; mod util; mod xz; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; /// Compression helpers. diff --git a/src/util/vec2d.rs b/src/util/vec2d.rs index f9272f5d..01d0b27b 100644 --- a/src/util/vec2d.rs +++ b/src/util/vec2d.rs @@ -1,3 +1,8 @@ +#[cfg(feature = "no_std")] +use alloc::boxed::Box; +#[cfg(feature = "no_std")] +use core::ops::{Index, IndexMut}; +#[cfg(not(feature = "no_std"))] use std::ops::{Index, IndexMut}; /// A 2 dimensional matrix in row-major order backed by a contiguous slice. diff --git a/src/xz/header.rs b/src/xz/header.rs index b1c28024..67a63fda 100644 --- a/src/xz/header.rs +++ b/src/xz/header.rs @@ -5,6 +5,10 @@ use crate::error; use crate::xz::crc::CRC32; use crate::xz::StreamFlags; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] +use std::io; /// File format magic header signature, see sect. 2.1.1.1. pub(crate) const XZ_MAGIC: &[u8] = &[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]; @@ -19,7 +23,7 @@ impl StreamHeader { /// Parse a Stream Header from a buffered reader. pub(crate) fn parse
(input: &mut BR) -> error::Result where - BR: std::io::BufRead, + BR: io::BufRead, { if !util::read_tag(input, XZ_MAGIC)? { return Err(error::Error::XzError(format!( diff --git a/src/xz/mod.rs b/src/xz/mod.rs index 3ded5d26..e0ee779f 100644 --- a/src/xz/mod.rs +++ b/src/xz/mod.rs @@ -5,6 +5,9 @@ //! [spec]: https://tukaani.org/xz/xz-file-format.txt use crate::error; +#[cfg(feature = "no_std")] +use core2::io; +#[cfg(not(feature = "no_std"))] use std::io; pub(crate) mod crc; @@ -85,12 +88,19 @@ impl From for u8 { mod test { use super::*; use byteorder::{BigEndian, ReadBytesExt}; - use std::io::{Seek, SeekFrom}; + #[cfg(feature = "no_std")] + use core::u8::MAX as U8_MAX; + #[cfg(feature = "no_std")] + use core2::io::{self, Seek, SeekFrom}; + #[cfg(not(feature = "no_std"))] + use std::io::{self, Seek, SeekFrom}; + #[cfg(not(feature = "no_std"))] + use std::u8::MAX as U8_MAX; #[test] fn test_checkmethod_roundtrip() { let mut count_valid = 0; - for input in 0..std::u8::MAX { + for input in 0..U8_MAX { if let Ok(check) = CheckMethod::try_from(input) { let output: u8 = check.into(); assert_eq!(input, output); @@ -106,7 +116,7 @@ mod test { check_method: CheckMethod::Crc32, }; - let mut cursor = std::io::Cursor::new(vec![0u8; 2]); + let mut cursor = io::Cursor::new(vec![0u8; 2]); let len = input.serialize(&mut cursor).unwrap(); assert_eq!(len, 2);