diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4ec05e6..7fd1da7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,7 +11,7 @@ jobs: matrix: os: [ubuntu-latest] rust: - - 1.55.0 # approximate MSRV is Stable -30 + - 1.60.0 # approximate MSRV is Stable -30 - stable - beta - nightly @@ -28,11 +28,11 @@ jobs: toolchain: ${{ matrix.rust }} # On MSRV some dev-dependencies don't build so we can't run tests. - name: Check MSRV - if: matrix.rust == '1.55.0' + if: matrix.rust == '1.60.0' run: | cargo check --features=alloc,std,grab_spare_slice - name: Test non nightly - if: matrix.rust != '1.55.0' && matrix.rust != 'nightly' + if: matrix.rust != '1.60.0' && matrix.rust != 'nightly' run: | cargo test --features=alloc,std,grab_spare_slice,latest_stable_rust - name: Test on Nightly with All Features diff --git a/Cargo.toml b/Cargo.toml index 819fd44..b65b0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ arbitrary = { version = "1", optional = true } borsh = { version = "1.2.0", optional = true, default-features = false } # Implements the trait `Array` for `GenericArray` struct. generic-array = { version = "1.1.1", optional = true, default-features = false } +# Provides derived `BitEncode` and `BitDecode` implementations +bin-proto = { version = "0.12.5", optional = true, default-features = false } [features] @@ -76,7 +78,7 @@ experimental_write_impl = [] real_blackbox = ["criterion/real_blackbox"] [package.metadata.docs.rs] -features = ["alloc", "std", "grab_spare_slice", "latest_stable_rust", "serde", "borsh"] +features = ["alloc", "std", "grab_spare_slice", "latest_stable_rust", "serde", "borsh", "bin-proto"] rustdoc-args = ["--cfg","docs_rs"] [package.metadata.playground] diff --git a/src/arrayvec.rs b/src/arrayvec.rs index c55796d..7ea625e 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -287,6 +287,81 @@ where } } +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitEncode for ArrayVec +where + A: Array, + ::Item: bin_proto::BitEncode, +{ + fn encode( + &self, write: &mut W, ctx: &mut Ctx, tag: bin_proto::Untagged, + ) -> bin_proto::Result<()> + where + W: bin_proto::BitWrite, + E: bin_proto::Endianness, + { + <[::Item] as bin_proto::BitEncode<_, _>>::encode::<_, E>( + self.as_slice(), + write, + ctx, + tag, + ) + } +} + +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitDecode> for ArrayVec +where + A: Array, + ::Item: bin_proto::BitDecode, + Tag: ::core::convert::TryInto, +{ + fn decode( + read: &mut R, ctx: &mut Ctx, tag: bin_proto::Tag, + ) -> bin_proto::Result + where + R: bin_proto::BitRead, + E: bin_proto::Endianness, + { + let item_count = + tag.0.try_into().map_err(|_| bin_proto::Error::TagConvert)?; + if item_count > A::CAPACITY { + return Err(bin_proto::Error::Other("insufficient capacity")); + } + let mut values = Self::default(); + for _ in 0..item_count { + values.push(bin_proto::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())?); + } + Ok(values) + } +} + +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitDecode for ArrayVec +where + A: Array, + ::Item: bin_proto::BitDecode, +{ + fn decode( + read: &mut R, ctx: &mut Ctx, _tag: bin_proto::Untagged, + ) -> bin_proto::Result + where + R: bin_proto::BitRead, + E: bin_proto::Endianness, + { + let mut values = Self::default(); + for item in bin_proto::util::decode_items_to_eof::<_, E, _, _>(read, ctx) { + if values.try_push(item?).is_some() { + return Err(bin_proto::Error::Other("insufficient capacity")); + } + } + Ok(values) + } +} + impl ArrayVec { /// Move all values from `other` into this vec. /// diff --git a/src/lib.rs b/src/lib.rs index 112853c..38e507a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,9 @@ //! * `borsh` provides a `BorshSerialize` and `BorshDeserialize` implementation //! for [`TinyVec`] and [`ArrayVec`] types, provided the inner item also has //! an implementation. +//! * `bin-proto` provides a `BitEncode` and `BitDecode` implementation for +//! [`TinyVec`] and [`ArrayVec`] types, provided the inner item also has an +//! implementation. //! //! ## API //! The general goal of the crate is that, as much as possible, the vecs here diff --git a/src/tinyvec.rs b/src/tinyvec.rs index d2bf218..63c8391 100644 --- a/src/tinyvec.rs +++ b/src/tinyvec.rs @@ -273,6 +273,72 @@ where } } +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitEncode for TinyVec +where + A: Array, + ::Item: bin_proto::BitEncode, +{ + fn encode( + &self, write: &mut W, ctx: &mut Ctx, tag: bin_proto::Untagged, + ) -> bin_proto::Result<()> + where + W: bin_proto::BitWrite, + E: bin_proto::Endianness, + { + <[::Item] as bin_proto::BitEncode<_, _>>::encode::<_, E>( + self.as_slice(), + write, + ctx, + tag, + ) + } +} + +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitDecode> for TinyVec +where + A: Array, + ::Item: bin_proto::BitDecode, + Tag: ::core::convert::TryInto, +{ + fn decode( + read: &mut R, ctx: &mut Ctx, tag: bin_proto::Tag, + ) -> bin_proto::Result + where + R: bin_proto::BitRead, + E: bin_proto::Endianness, + { + let item_count = + tag.0.try_into().map_err(|_| bin_proto::Error::TagConvert)?; + let mut values = Self::with_capacity(item_count); + for _ in 0..item_count { + values.push(bin_proto::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())?); + } + Ok(values) + } +} + +#[cfg(feature = "bin-proto")] +#[cfg_attr(docs_rs, doc(cfg(feature = "bin-proto")))] +impl bin_proto::BitDecode for TinyVec +where + A: Array, + ::Item: bin_proto::BitDecode, +{ + fn decode( + read: &mut R, ctx: &mut Ctx, _tag: bin_proto::Untagged, + ) -> bin_proto::Result + where + R: bin_proto::BitRead, + E: bin_proto::Endianness, + { + bin_proto::util::decode_items_to_eof::<_, E, _, _>(read, ctx).collect() + } +} + impl TinyVec { /// Returns whether elements are on heap #[inline(always)] diff --git a/tests/arrayvec.rs b/tests/arrayvec.rs index fa08273..f74d7e2 100644 --- a/tests/arrayvec.rs +++ b/tests/arrayvec.rs @@ -1,6 +1,8 @@ #![allow(bad_style)] #![allow(clippy::clone_on_copy)] +#[cfg(feature = "bin-proto")] +use bin_proto::{BigEndian, BitDecodeExt, BitEncodeExt, Tag, Untagged}; #[cfg(feature = "serde")] use serde_test::{assert_tokens, Token}; use std::iter::FromIterator; @@ -469,6 +471,79 @@ fn ArrayVec_borsh_de() { assert_eq!(tv, des); } +#[cfg(feature = "bin-proto")] +#[test] +fn ArrayVec_bin_proto_encode_untagged() { + let mut values = ArrayVec::<[u8; 4]>::new(); + values.push(0x12); + values.push(0x34); + values.push(0x56); + let mut data = [0u8; 16]; + let n_bytes = values + .encode_bytes_ctx_buf(BigEndian, &mut (), Untagged, &mut data) + .unwrap() as usize; + assert_eq!(&[0x12, 0x34, 0x56], &data[0..n_bytes]); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn ArrayVec_bin_proto_decode_tagged_too_long() { + assert!(ArrayVec::<[u8; 4]>::decode_bytes_ctx( + &[0x12, 0x34, 0x56, 0x78, 0x9A], + BigEndian, + &mut (), + Tag(5usize), + ) + .is_err()); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn ArrayVec_bin_proto_decode_tagged() { + let (decoded, read_bits) = ArrayVec::<[u8; 4]>::decode_bytes_ctx( + &[0x12, 0x34, 0x56, 0x78], + BigEndian, + &mut (), + Tag(3usize), + ) + .unwrap(); + let mut expected = ArrayVec::<[u8; 4]>::new(); + expected.push(0x12); + expected.push(0x34); + expected.push(0x56); + assert_eq!(24, read_bits); + assert_eq!(expected, decoded); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn ArrayVec_bin_proto_decode_untagged_too_long() { + let result = ArrayVec::<[u8; 4]>::decode_all_bytes_ctx( + &[0x12, 0x34, 0x56, 0x78, 0x9A], + BigEndian, + &mut (), + Untagged, + ); + assert!(result.is_err()); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn ArrayVec_bin_proto_decode_untagged() { + let decoded = ArrayVec::<[u8; 4]>::decode_all_bytes_ctx( + &[0x12, 0x34, 0x56], + BigEndian, + &mut (), + Untagged, + ) + .unwrap(); + let mut expected = ArrayVec::<[u8; 4]>::new(); + expected.push(0x12); + expected.push(0x34); + expected.push(0x56); + assert_eq!(expected, decoded); +} + #[test] fn ArrayVec_try_from_slice() { use std::convert::TryFrom; diff --git a/tests/tinyvec.rs b/tests/tinyvec.rs index 68c4696..12311ef 100644 --- a/tests/tinyvec.rs +++ b/tests/tinyvec.rs @@ -2,6 +2,8 @@ #![allow(bad_style)] #![allow(clippy::redundant_clone)] +#[cfg(feature = "bin-proto")] +use bin_proto::{BigEndian, BitDecodeExt, BitEncodeExt, Tag, Untagged}; #[cfg(feature = "serde")] use serde_test::{assert_tokens, Token}; use std::iter::FromIterator; @@ -476,6 +478,55 @@ fn TinyVec_borsh_de_heap() { assert_eq!(tv, des); } +#[cfg(feature = "bin-proto")] +#[test] +fn TinyVec_bin_proto_encode_untagged() { + let mut values = TinyVec::<[u8; 2]>::new(); + values.push(0x12); + values.push(0x34); + values.push(0x56); + let mut data = [0u8; 16]; + let n_bytes = values + .encode_bytes_ctx_buf(BigEndian, &mut (), Untagged, &mut data) + .unwrap() as usize; + assert_eq!(&[0x12, 0x34, 0x56], &data[0..n_bytes]); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn TinyVec_bin_proto_decode_tagged() { + let (decoded, read_bits) = TinyVec::<[u8; 2]>::decode_bytes_ctx( + &[0x12, 0x34, 0x56, 0x78], + BigEndian, + &mut (), + Tag(3usize), + ) + .unwrap(); + let mut expected = TinyVec::<[u8; 2]>::new(); + expected.push(0x12); + expected.push(0x34); + expected.push(0x56); + assert_eq!(24, read_bits); + assert_eq!(expected, decoded); +} + +#[cfg(feature = "bin-proto")] +#[test] +fn TinyVec_bin_proto_decode_untagged() { + let decoded = TinyVec::<[u8; 2]>::decode_all_bytes_ctx( + &[0x12, 0x34, 0x56], + BigEndian, + &mut (), + Untagged, + ) + .unwrap(); + let mut expected = TinyVec::<[u8; 2]>::new(); + expected.push(0x12); + expected.push(0x34); + expected.push(0x56); + assert_eq!(expected, decoded); +} + #[test] fn TinyVec_pretty_debug() { let tv: TinyVec<[i32; 6]> = tiny_vec![1, 2, 3];