├── .gitignore ├── Cargo.toml ├── bench ├── Cargo.toml └── src │ └── lib.rs ├── bin-proto ├── src │ ├── impls │ │ ├── mod.rs │ │ ├── reference.rs │ │ ├── mut_reference.rs │ │ ├── unit.rs │ │ ├── phantom_pinned.rs │ │ ├── phantom_data.rs │ │ ├── ipv4.rs │ │ ├── ipv6.rs │ │ ├── cstring.rs │ │ ├── cstr.rs │ │ ├── option.rs │ │ ├── string.rs │ │ ├── newtype.rs │ │ ├── str_.rs │ │ ├── array.rs │ │ ├── slice.rs │ │ ├── tuple.rs │ │ ├── map.rs │ │ ├── container.rs │ │ ├── list.rs │ │ └── numerics.rs │ ├── discriminable.rs │ ├── util.rs │ ├── error.rs │ ├── codec.rs │ └── lib.rs ├── tests │ ├── flexible_array_member.rs │ ├── ipv4.rs │ ├── skip.rs │ ├── ctx.rs │ ├── enums.rs │ ├── structs.rs │ └── tag.rs └── Cargo.toml ├── bin-proto-derive ├── Cargo.toml └── src │ ├── enums.rs │ ├── codegen │ ├── trait_impl.rs │ ├── enums.rs │ └── mod.rs │ ├── lib.rs │ └── attr.rs ├── LICENSE.txt ├── .github └── workflows │ └── ci.yml ├── README.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.profraw 4 | *.profdata 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "bin-proto", 4 | "bin-proto-derive", 5 | ] 6 | exclude = [ 7 | "bench", 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bin-proto-bench" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bin-proto = { path = "../bin-proto" } 9 | deku = "0.19.0" 10 | -------------------------------------------------------------------------------- /bin-proto/src/impls/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementations 2 | 3 | mod array; 4 | mod container; 5 | mod cstr; 6 | mod cstring; 7 | mod ipv4; 8 | mod ipv6; 9 | mod list; 10 | mod map; 11 | mod mut_reference; 12 | mod newtype; 13 | mod numerics; 14 | mod option; 15 | mod phantom_data; 16 | mod phantom_pinned; 17 | mod reference; 18 | mod slice; 19 | mod str_; 20 | mod string; 21 | mod tuple; 22 | mod unit; 23 | -------------------------------------------------------------------------------- /bin-proto/src/discriminable.rs: -------------------------------------------------------------------------------- 1 | /// A trait for types with discriminants. Automatically derived for `enum`s. 2 | pub trait Discriminable { 3 | /// The type of the discriminant. 4 | type Discriminant; 5 | 6 | /// Returns a value uniquely identifying the variant. 7 | /// 8 | /// Returns [`None`] if the variant cannot be encoded. 9 | fn discriminant(&self) -> Option; 10 | } 11 | -------------------------------------------------------------------------------- /bin-proto/src/impls/reference.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitWrite, Endianness}; 2 | 3 | use crate::{BitEncode, Result}; 4 | 5 | impl BitEncode for &T 6 | where 7 | T: BitEncode, 8 | { 9 | fn encode(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()> 10 | where 11 | W: BitWrite, 12 | E: Endianness, 13 | { 14 | (**self).encode::<_, E>(write, ctx, tag) 15 | } 16 | } 17 | 18 | test_encode!(&u8; &1 => [0x01]); 19 | -------------------------------------------------------------------------------- /bin-proto/src/impls/mut_reference.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitWrite, Endianness}; 2 | 3 | use crate::{BitEncode, Result}; 4 | 5 | impl BitEncode for &mut T 6 | where 7 | T: BitEncode, 8 | { 9 | fn encode(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()> 10 | where 11 | W: BitWrite, 12 | E: Endianness, 13 | { 14 | (**self).encode::<_, E>(write, ctx, tag) 15 | } 16 | } 17 | 18 | test_encode!(&mut u8; &mut 1 => [0x01]); 19 | -------------------------------------------------------------------------------- /bin-proto/src/impls/unit.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{BitDecode, BitEncode, Result}; 4 | 5 | impl BitDecode for () { 6 | fn decode(_: &mut R, _: &mut Ctx, (): ()) -> Result 7 | where 8 | R: BitRead, 9 | E: Endianness, 10 | { 11 | Ok(()) 12 | } 13 | } 14 | 15 | impl BitEncode for () { 16 | fn encode(&self, _: &mut W, _: &mut Ctx, (): ()) -> Result<()> 17 | where 18 | W: BitWrite, 19 | E: Endianness, 20 | { 21 | Ok(()) 22 | } 23 | } 24 | 25 | test_codec!((); () => []); 26 | test_roundtrip!(()); 27 | -------------------------------------------------------------------------------- /bin-proto-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bin-proto-derive" 3 | version = "0.12.0" 4 | authors = ["Wojciech Graj "] 5 | edition = "2021" 6 | rust-version = "1.83.0" 7 | 8 | description = "Derive macros for bin-proto" 9 | license = "MIT" 10 | readme = "../README.md" 11 | repository = "https://github.com/wojciech-graj/bin-proto" 12 | documentation = "https://docs.rs/bin-proto" 13 | keywords = ["binary", "encode", "decode", "serialize", "deserialize"] 14 | categories = ["encoding", "parsing"] 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | [dependencies] 20 | syn = { version = "2.0.32", features = ["full"] } 21 | quote = "1.0.0" 22 | proc-macro2 = "1.0.0" 23 | -------------------------------------------------------------------------------- /bin-proto/src/impls/phantom_pinned.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{BitDecode, BitEncode, Result}; 4 | use core::marker::PhantomPinned; 5 | 6 | impl BitDecode for PhantomPinned { 7 | fn decode(_: &mut R, _: &mut Ctx, (): ()) -> Result 8 | where 9 | R: BitRead, 10 | E: Endianness, 11 | { 12 | Ok(Self) 13 | } 14 | } 15 | 16 | impl BitEncode for PhantomPinned { 17 | fn encode(&self, _: &mut W, _: &mut Ctx, (): ()) -> Result<()> 18 | where 19 | W: BitWrite, 20 | E: Endianness, 21 | { 22 | Ok(()) 23 | } 24 | } 25 | 26 | test_codec!(PhantomPinned; PhantomPinned => []); 27 | -------------------------------------------------------------------------------- /bin-proto/tests/flexible_array_member.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 4 | use bitstream_io::BigEndian; 5 | 6 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 7 | struct WithFlexibleArrayMember(#[bin_proto(untagged)] Vec); 8 | 9 | #[test] 10 | fn decode_untagged() { 11 | assert_eq!( 12 | WithFlexibleArrayMember::decode_bytes(&[1, 2, 3], BigEndian).unwrap(), 13 | (WithFlexibleArrayMember(vec![1, 2, 3]), 24) 14 | ); 15 | } 16 | 17 | #[test] 18 | fn encodes_untagged() { 19 | assert_eq!( 20 | WithFlexibleArrayMember(vec![1, 2, 3]) 21 | .encode_bytes(BigEndian) 22 | .unwrap(), 23 | vec![1, 2, 3] 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /bin-proto/src/impls/phantom_data.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{BitDecode, BitEncode, Result}; 4 | use core::marker::PhantomData; 5 | 6 | impl BitDecode for PhantomData { 7 | fn decode(_: &mut R, _: &mut Ctx, (): ()) -> Result 8 | where 9 | R: BitRead, 10 | E: Endianness, 11 | { 12 | Ok(Self) 13 | } 14 | } 15 | 16 | impl BitEncode for PhantomData { 17 | fn encode(&self, _: &mut W, _: &mut Ctx, (): ()) -> Result<()> 18 | where 19 | W: BitWrite, 20 | E: Endianness, 21 | { 22 | Ok(()) 23 | } 24 | } 25 | 26 | test_codec!(PhantomData; PhantomData => []); 27 | test_roundtrip!(PhantomData); 28 | -------------------------------------------------------------------------------- /bin-proto/src/impls/ipv4.rs: -------------------------------------------------------------------------------- 1 | use core::net::Ipv4Addr; 2 | 3 | use bitstream_io::{BitRead, BitWrite, Endianness}; 4 | 5 | use crate::{BitDecode, BitEncode, Result}; 6 | 7 | impl BitDecode for Ipv4Addr { 8 | fn decode(read: &mut R, ctx: &mut Ctx, (): ()) -> Result 9 | where 10 | R: BitRead, 11 | E: Endianness, 12 | { 13 | u32::decode::<_, E>(read, ctx, ()).map(Self::from_bits) 14 | } 15 | } 16 | 17 | impl BitEncode for Ipv4Addr { 18 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 19 | where 20 | W: BitWrite, 21 | E: Endianness, 22 | { 23 | self.to_bits().encode::<_, E>(write, ctx, ()) 24 | } 25 | } 26 | 27 | test_codec!(Ipv4Addr; Ipv4Addr::new(192, 168, 1, 0) => [192, 168, 1, 0]); 28 | test_roundtrip!(Ipv4Addr); 29 | -------------------------------------------------------------------------------- /bin-proto/src/impls/ipv6.rs: -------------------------------------------------------------------------------- 1 | use core::net::Ipv6Addr; 2 | 3 | use bitstream_io::{BitRead, BitWrite, Endianness}; 4 | 5 | use crate::{BitDecode, BitEncode, Result}; 6 | 7 | impl BitDecode for Ipv6Addr { 8 | fn decode(read: &mut R, ctx: &mut Ctx, (): ()) -> Result 9 | where 10 | R: BitRead, 11 | E: Endianness, 12 | { 13 | u128::decode::<_, E>(read, ctx, ()).map(Self::from_bits) 14 | } 15 | } 16 | 17 | impl BitEncode for Ipv6Addr { 18 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 19 | where 20 | W: BitWrite, 21 | E: Endianness, 22 | { 23 | self.to_bits().encode::<_, E>(write, ctx, ()) 24 | } 25 | } 26 | 27 | test_codec!(Ipv6Addr; 28 | Ipv6Addr::new(0x2001, 0x0db8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334) => 29 | [ 30 | 0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 31 | 0x34 32 | ] 33 | ); 34 | test_roundtrip!(Ipv6Addr); 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Dylan McKay 2 | Copyright (c) 2024-2025 Wojciech Graj 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /bin-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bin-proto" 3 | version = "0.12.2" 4 | authors = ["Wojciech Graj "] 5 | edition = "2021" 6 | rust-version = "1.83.0" 7 | 8 | description = "Conversion to/from binary for arbitrary types" 9 | license = "MIT" 10 | readme = "../README.md" 11 | repository = "https://github.com/wojciech-graj/bin-proto" 12 | documentation = "https://docs.rs/bin-proto" 13 | keywords = ["binary", "encode", "decode", "serialize", "deserialize"] 14 | categories = ["encoding", "parsing"] 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | rustdoc-args = ["--cfg", "docsrs"] 19 | 20 | [features] 21 | alloc = ["bitstream-io/alloc", "core2/alloc"] 22 | default = ["alloc", "derive", "std"] 23 | derive = ["dep:bin-proto-derive"] 24 | std = ["bitstream-io/std", "core2/std", "alloc"] 25 | 26 | [dependencies] 27 | bin-proto-derive = { version = "0.12.0", path = "../bin-proto-derive", optional = true } 28 | bitstream-io = { version = "4.6.0", default-features = false } 29 | core2 = { version = "0.4.0", default-features = false } 30 | 31 | [dev-dependencies] 32 | proptest = "1.8.0" 33 | -------------------------------------------------------------------------------- /bin-proto/src/impls/cstring.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "alloc")] 2 | 3 | use alloc::{ffi::CString, vec::Vec}; 4 | use bitstream_io::{BitRead, BitWrite, Endianness}; 5 | 6 | use crate::{util, BitDecode, BitEncode, Result}; 7 | 8 | impl BitDecode for CString { 9 | fn decode(read: &mut R, ctx: &mut Ctx, tag: ()) -> Result 10 | where 11 | R: BitRead, 12 | E: Endianness, 13 | { 14 | let mut result = Vec::new(); 15 | loop { 16 | let c: u8 = BitDecode::decode::<_, E>(read, ctx, tag)?; 17 | if c == 0x00 { 18 | return Ok(Self::new(result)?); 19 | } 20 | result.push(c); 21 | } 22 | } 23 | } 24 | 25 | impl BitEncode for CString { 26 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 27 | where 28 | W: BitWrite, 29 | E: Endianness, 30 | { 31 | util::encode_items::<_, E, _, _>(self.to_bytes_with_nul().iter(), write, ctx) 32 | } 33 | } 34 | 35 | test_codec!(CString; CString::new("ABC").unwrap() => [0x41, 0x42, 0x43, 0]); 36 | test_roundtrip!(CString); 37 | -------------------------------------------------------------------------------- /bin-proto/src/impls/cstr.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::CStr; 2 | 3 | use bitstream_io::{BitWrite, Endianness}; 4 | 5 | use crate::{util, BitEncode, Result}; 6 | 7 | impl BitEncode for CStr { 8 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 9 | where 10 | W: BitWrite, 11 | E: Endianness, 12 | { 13 | util::encode_items::<_, E, _, _>(self.to_bytes_with_nul().iter(), write, ctx) 14 | } 15 | } 16 | 17 | #[cfg(feature = "alloc")] 18 | #[allow(clippy::wildcard_imports)] 19 | mod decode { 20 | use alloc::{boxed::Box, ffi::CString}; 21 | use bitstream_io::BitRead; 22 | 23 | use crate::BitDecode; 24 | 25 | use super::*; 26 | 27 | impl BitDecode for Box { 28 | fn decode(read: &mut R, ctx: &mut Ctx, tag: ()) -> Result 29 | where 30 | R: BitRead, 31 | E: Endianness, 32 | { 33 | CString::decode::<_, E>(read, ctx, tag).map(Into::into) 34 | } 35 | } 36 | 37 | test_decode!(Box; [0x41, 0x42, 0x43, 0] => CString::new("ABC").unwrap().into()); 38 | } 39 | 40 | test_encode!( 41 | &CStr; CStr::from_bytes_with_nul(&[0x41, 0x42, 0x43, 0]).unwrap() => [0x41, 0x42, 0x43, 0] 42 | ); 43 | -------------------------------------------------------------------------------- /bin-proto/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions for dealing with iterators 2 | 3 | use crate::{BitDecode, BitEncode, Error, Result}; 4 | 5 | use bitstream_io::{BitRead, BitWrite, Endianness}; 6 | use core::iter; 7 | #[cfg(feature = "std")] 8 | use std::io; 9 | 10 | #[cfg(not(feature = "std"))] 11 | use core2::io; 12 | 13 | /// [`BitEncode`]s an iterator of parcels to the stream. 14 | /// 15 | /// Does not include a length prefix. 16 | pub fn encode_items<'a, W, E, Ctx, T>( 17 | items: impl IntoIterator, 18 | write: &mut W, 19 | ctx: &mut Ctx, 20 | ) -> Result<()> 21 | where 22 | W: BitWrite, 23 | E: Endianness, 24 | T: BitEncode + 'a, 25 | { 26 | for item in items { 27 | item.encode::<_, E>(write, ctx, ())?; 28 | } 29 | Ok(()) 30 | } 31 | 32 | #[allow(unused)] 33 | pub fn decode_items_to_eof<'a, R, E, Ctx, T>( 34 | read: &'a mut R, 35 | ctx: &'a mut Ctx, 36 | ) -> impl Iterator> + use<'a, R, E, Ctx, T> 37 | where 38 | R: BitRead, 39 | E: Endianness, 40 | T: BitDecode, 41 | { 42 | iter::from_fn(|| match T::decode::<_, E>(read, ctx, ()) { 43 | Err(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => None, 44 | other => Some(other), 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | name: Test and Benchmark 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Rust - Stable 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | profile: minimal 21 | components: clippy 22 | 23 | - name: Set up Rust - Nightly 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: nightly 27 | profile: minimal 28 | 29 | - name: Install cargo-binstall 30 | uses: cargo-bins/cargo-binstall@main 31 | 32 | - name: Test 33 | run: cargo test 34 | 35 | - name: Test (no_std) 36 | run: cargo test --no-default-features --features derive 37 | 38 | - name: Test (no_std, alloc) 39 | run: cargo test --no-default-features --features derive,alloc 40 | 41 | - name: Clippy 42 | run: cargo clippy 43 | 44 | - name: Bench 45 | working-directory: bench 46 | run: cargo +nightly bench 47 | 48 | - name: Install cargo-msrv 49 | run: cargo binstall --no-confirm cargo-msrv 50 | 51 | - name: MSRV 52 | run: | 53 | cargo +nightly generate-lockfile -Z minimal-versions 54 | cd bin-proto 55 | cargo msrv verify 56 | -------------------------------------------------------------------------------- /bin-proto/src/impls/option.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{BitDecode, BitEncode, Error, Result, Untagged}; 4 | 5 | impl BitDecode> for Option 6 | where 7 | T: BitDecode, 8 | Tag: TryInto, 9 | { 10 | fn decode(read: &mut R, ctx: &mut Ctx, tag: crate::Tag) -> Result 11 | where 12 | R: BitRead, 13 | E: Endianness, 14 | { 15 | if tag.0.try_into().map_err(|_| Error::TagConvert)? { 16 | let value = T::decode::<_, E>(read, ctx, ())?; 17 | Ok(Some(value)) 18 | } else { 19 | Ok(None) 20 | } 21 | } 22 | } 23 | 24 | impl BitEncode for Option 25 | where 26 | T: BitEncode, 27 | { 28 | fn encode(&self, write: &mut W, ctx: &mut Ctx, _: Untagged) -> Result<()> 29 | where 30 | W: BitWrite, 31 | E: Endianness, 32 | { 33 | if let Some(ref value) = *self { 34 | value.encode::<_, E>(write, ctx, ())?; 35 | } 36 | Ok(()) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod none { 42 | use crate::Tag; 43 | 44 | use super::*; 45 | 46 | test_codec!(Option| Untagged, Tag(false); None => []); 47 | } 48 | 49 | #[cfg(test)] 50 | mod some { 51 | use crate::Tag; 52 | 53 | use super::*; 54 | 55 | test_codec!(Option| Untagged, Tag(true); Some(1) => [0x01]); 56 | } 57 | -------------------------------------------------------------------------------- /bin-proto/src/impls/string.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "alloc")] 2 | 3 | use crate::{util, BitDecode, BitEncode, Error, Result, Untagged}; 4 | 5 | use alloc::{string::String, vec::Vec}; 6 | use bitstream_io::{BitRead, BitWrite, Endianness}; 7 | 8 | impl BitDecode> for String 9 | where 10 | Tag: TryInto, 11 | { 12 | fn decode(read: &mut R, ctx: &mut Ctx, tag: crate::Tag) -> Result 13 | where 14 | R: BitRead, 15 | E: Endianness, 16 | { 17 | let item_count = tag.0.try_into().map_err(|_| Error::TagConvert)?; 18 | let mut bytes = Vec::with_capacity(item_count); 19 | for _ in 0..item_count { 20 | bytes.push(u8::decode::<_, E>(read, ctx, ())?); 21 | } 22 | Ok(Self::from_utf8(bytes)?) 23 | } 24 | } 25 | 26 | impl BitEncode for String { 27 | fn encode(&self, write: &mut W, ctx: &mut Ctx, _: Untagged) -> Result<()> 28 | where 29 | W: BitWrite, 30 | E: Endianness, 31 | { 32 | util::encode_items::<_, E, _, _>(self.as_bytes(), write, ctx) 33 | } 34 | } 35 | 36 | impl BitDecode for String { 37 | fn decode(read: &mut R, ctx: &mut Ctx, _: Untagged) -> Result 38 | where 39 | R: BitRead, 40 | E: Endianness, 41 | { 42 | let bytes = util::decode_items_to_eof::<_, E, _, _>(read, ctx).collect::>()?; 43 | Ok(Self::from_utf8(bytes)?) 44 | } 45 | } 46 | 47 | test_untagged_and_codec!(String| Untagged, crate::Tag(3); "abc".into() => [b'a', b'b', b'c']); 48 | -------------------------------------------------------------------------------- /bin-proto/src/impls/newtype.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_newtype { 2 | ($ty:ident) => { 3 | impl $crate::BitDecode for $ty 4 | where 5 | T: $crate::BitDecode, 6 | { 7 | fn decode( 8 | read: &mut R, 9 | ctx: &mut Ctx, 10 | tag: Tag, 11 | ) -> $crate::Result 12 | where 13 | R: ::bitstream_io::BitRead, 14 | E: ::bitstream_io::Endianness, 15 | { 16 | Ok(Self($crate::BitDecode::decode::<_, E>(read, ctx, tag)?)) 17 | } 18 | } 19 | 20 | impl $crate::BitEncode for $ty 21 | where 22 | T: $crate::BitEncode, 23 | { 24 | fn encode( 25 | &self, 26 | write: &mut W, 27 | ctx: &mut Ctx, 28 | tag: Tag 29 | ) -> $crate::Result<()> 30 | where 31 | W: ::bitstream_io::BitWrite, 32 | E: ::bitstream_io::Endianness, 33 | { 34 | $crate::BitEncode::encode::<_, E>(&self.0, write, ctx, tag) 35 | } 36 | } 37 | 38 | test_codec!($ty; $ty(1u8) => [0x01]); 39 | }; 40 | } 41 | 42 | mod wrapping { 43 | use core::num::Wrapping; 44 | 45 | impl_newtype!(Wrapping); 46 | test_roundtrip!(Wrapping); 47 | } 48 | 49 | mod saturating { 50 | use core::num::Saturating; 51 | 52 | impl_newtype!(Saturating); 53 | // TODO: https://github.com/proptest-rs/proptest/pull/585 54 | } 55 | -------------------------------------------------------------------------------- /bin-proto/src/impls/str_.rs: -------------------------------------------------------------------------------- 1 | use crate::{util, BitEncode, Result}; 2 | 3 | use bitstream_io::{BitWrite, Endianness}; 4 | 5 | impl BitEncode for str { 6 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 7 | where 8 | W: BitWrite, 9 | E: Endianness, 10 | { 11 | util::encode_items::<_, E, _, _>(self.as_bytes(), write, ctx) 12 | } 13 | } 14 | 15 | #[cfg(feature = "alloc")] 16 | #[allow(clippy::wildcard_imports)] 17 | mod decode { 18 | use alloc::{boxed::Box, string::String}; 19 | use bitstream_io::BitRead; 20 | 21 | use crate::{BitDecode, Untagged}; 22 | 23 | use super::*; 24 | 25 | impl BitDecode for Box { 26 | fn decode(read: &mut R, ctx: &mut Ctx, tag: Untagged) -> Result 27 | where 28 | R: BitRead, 29 | E: Endianness, 30 | { 31 | String::decode::<_, E>(read, ctx, tag).map(Into::into) 32 | } 33 | } 34 | 35 | impl BitDecode> for Box 36 | where 37 | Tag: TryInto, 38 | { 39 | fn decode(read: &mut R, ctx: &mut Ctx, tag: crate::Tag) -> Result 40 | where 41 | R: BitRead, 42 | E: Endianness, 43 | { 44 | String::decode::<_, E>(read, ctx, tag).map(Into::into) 45 | } 46 | } 47 | 48 | test_decode!(Box| crate::Tag(3); [b'a', b'b', b'c'] => "abc".into()); 49 | 50 | #[cfg(test)] 51 | mod untagged { 52 | use super::*; 53 | 54 | test_decode!(Box| Untagged; [b'a', b'b', b'c'] => "abc".into()); 55 | } 56 | } 57 | 58 | test_encode!(&str; "abc" => [b'a', b'b', b'c']); 59 | -------------------------------------------------------------------------------- /bin-proto/src/impls/array.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{util, BitDecode, BitEncode, Result}; 4 | use core::mem::MaybeUninit; 5 | 6 | struct PartialGuard { 7 | ptr: *mut T, 8 | len: usize, 9 | } 10 | 11 | impl Drop for PartialGuard { 12 | fn drop(&mut self) { 13 | unsafe { 14 | core::ptr::drop_in_place(core::ptr::slice_from_raw_parts_mut(self.ptr, self.len)); 15 | } 16 | } 17 | } 18 | 19 | impl BitDecode for [T; N] 20 | where 21 | T: BitDecode, 22 | { 23 | fn decode(read: &mut R, ctx: &mut Ctx, (): ()) -> Result 24 | where 25 | R: BitRead, 26 | E: Endianness, 27 | { 28 | let mut array: MaybeUninit<[T; N]> = MaybeUninit::uninit(); 29 | let mut guard = PartialGuard { 30 | ptr: array.as_mut_ptr().cast::(), 31 | len: 0, 32 | }; 33 | while guard.len < N { 34 | let item = T::decode::<_, E>(read, ctx, ())?; 35 | unsafe { 36 | guard.ptr.add(guard.len).write(item); 37 | } 38 | guard.len += 1; 39 | } 40 | core::mem::forget(guard); 41 | Ok(unsafe { array.assume_init() }) 42 | } 43 | } 44 | 45 | impl BitEncode for [T; N] 46 | where 47 | T: BitEncode + Sized, 48 | { 49 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 50 | where 51 | W: BitWrite, 52 | E: Endianness, 53 | { 54 | util::encode_items::<_, E, _, _>(self.iter(), write, ctx) 55 | } 56 | } 57 | 58 | test_codec!([u8; 4]; [0, 1, 2, 3] => [0x00, 0x01, 0x02, 0x03]); 59 | test_roundtrip!([u8; 4]); 60 | -------------------------------------------------------------------------------- /bin-proto/src/impls/slice.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitWrite, Endianness}; 2 | 3 | use crate::{util, BitEncode, Result}; 4 | 5 | impl BitEncode for [T] 6 | where 7 | T: BitEncode, 8 | { 9 | fn encode(&self, write: &mut W, ctx: &mut Ctx, (): ()) -> Result<()> 10 | where 11 | W: BitWrite, 12 | E: Endianness, 13 | { 14 | util::encode_items::<_, E, _, _>(self.iter(), write, ctx) 15 | } 16 | } 17 | 18 | #[cfg(feature = "alloc")] 19 | #[allow(clippy::wildcard_imports)] 20 | mod decode { 21 | use alloc::{boxed::Box, vec::Vec}; 22 | use bitstream_io::BitRead; 23 | 24 | use crate::{BitDecode, Untagged}; 25 | 26 | use super::*; 27 | 28 | impl BitDecode for Box<[T]> 29 | where 30 | T: BitDecode, 31 | { 32 | fn decode(read: &mut R, ctx: &mut Ctx, tag: Untagged) -> Result 33 | where 34 | R: BitRead, 35 | E: Endianness, 36 | { 37 | Vec::decode::<_, E>(read, ctx, tag).map(Into::into) 38 | } 39 | } 40 | 41 | impl BitDecode> for Box<[T]> 42 | where 43 | T: BitDecode, 44 | Tag: TryInto, 45 | { 46 | fn decode(read: &mut R, ctx: &mut Ctx, tag: crate::Tag) -> Result 47 | where 48 | R: BitRead, 49 | E: Endianness, 50 | { 51 | Vec::decode::<_, E>(read, ctx, tag).map(Into::into) 52 | } 53 | } 54 | 55 | test_decode!(Box<[u8]>| crate::Tag(3); [0x01, 0x02, 0x03] => Box::new([1, 2, 3])); 56 | 57 | #[cfg(test)] 58 | mod untagged { 59 | use super::*; 60 | 61 | test_decode!(Box<[u8]>| Untagged; [0x01, 0x02, 0x03] => Box::new([1, 2, 3])); 62 | } 63 | } 64 | 65 | test_encode!(&[u8]; &[1, 2, 3] => [0x01, 0x02, 0x03]); 66 | -------------------------------------------------------------------------------- /bin-proto-derive/src/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::attr::{AttrKind, Attrs}; 2 | use syn::{spanned::Spanned, Error, Result}; 3 | 4 | pub struct Enum { 5 | pub discriminant_ty: syn::Type, 6 | pub variants: Vec, 7 | } 8 | 9 | pub struct EnumVariant { 10 | pub ident: syn::Ident, 11 | pub discriminant_value: Option, 12 | pub discriminant_other: bool, 13 | pub skip_encode: bool, 14 | pub skip_decode: bool, 15 | pub fields: syn::Fields, 16 | } 17 | 18 | impl Enum { 19 | pub fn try_new(ast: &syn::DeriveInput, e: &syn::DataEnum) -> Result { 20 | let attrs = Attrs::parse(ast.attrs.as_slice(), Some(AttrKind::Enum), ast.span())?; 21 | 22 | Ok(Self { 23 | discriminant_ty: attrs.discriminant_type.ok_or_else(|| { 24 | Error::new(ast.span(), "enum missing 'discriminant_type' attribute.") 25 | })?, 26 | variants: e 27 | .variants 28 | .iter() 29 | .map(|variant| { 30 | let attrs = Attrs::parse( 31 | variant.attrs.as_slice(), 32 | Some(AttrKind::Variant), 33 | variant.span(), 34 | )?; 35 | 36 | Ok(EnumVariant { 37 | ident: variant.ident.clone(), 38 | discriminant_value: attrs 39 | .discriminant 40 | .or_else(|| variant.discriminant.as_ref().map(|a| a.1.clone())), 41 | discriminant_other: attrs.other, 42 | skip_encode: attrs.skip_encode, 43 | skip_decode: attrs.skip_decode, 44 | fields: variant.fields.clone(), 45 | }) 46 | }) 47 | .collect::>()?, 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bin-proto/tests/ipv4.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 4 | use bitstream_io::BigEndian; 5 | 6 | #[derive(Debug, Copy, Clone, BitDecode, BitEncode, PartialEq)] 7 | #[bin_proto(discriminant_type = u8)] 8 | #[bin_proto(bits = 4)] 9 | enum Version { 10 | V4 = 4, 11 | } 12 | 13 | #[derive(Debug, Copy, Clone, BitDecode, BitEncode, PartialEq)] 14 | struct Flags { 15 | #[bin_proto(bits = 1)] 16 | reserved: bool, 17 | #[bin_proto(bits = 1)] 18 | dont_fragment: bool, 19 | #[bin_proto(bits = 1)] 20 | more_fragments: bool, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone, BitDecode, BitEncode, PartialEq)] 24 | struct IPv4 { 25 | version: Version, 26 | #[bin_proto(bits = 4)] 27 | internet_header_length: u8, 28 | #[bin_proto(bits = 6)] 29 | differentiated_services_code_point: u8, 30 | #[bin_proto(bits = 2)] 31 | explicit_congestion_notification: u8, 32 | total_length: u16, 33 | identification: u16, 34 | flags: Flags, 35 | #[bin_proto(bits = 13)] 36 | fragment_offset: u16, 37 | time_to_live: u8, 38 | protocol: u8, 39 | header_checksum: u16, 40 | source_address: [u8; 4], 41 | destination_address: [u8; 4], 42 | } 43 | 44 | #[test] 45 | fn can_encode_decode_ipv4() { 46 | let raw = [ 47 | 0b0100_0000 // Version: 4 48 | | 0b0101, // Header Length: 5, 49 | 0x00, // Differentiated Services Codepoint: 0, Explicit Congestion Notification: 0 50 | 0x05, 51 | 0x94, // Total Length: 1428 52 | 0x83, 53 | 0xf6, // Identification: 0x83f6 54 | 0b0100_0000 // Flags: Don't Fragment 55 | | 0b0_0000, 56 | 0x00, // Fragment Offset: 0 57 | 0x40, // Time to Live: 64 58 | 0x01, // Protocol: 1 59 | 0xeb, 60 | 0x6e, // Header Checksum: 0xeb6e 61 | 0x02, 62 | 0x01, 63 | 0x01, 64 | 0x01, // Source Address: 2.1.1.1 65 | 0x02, 66 | 0x01, 67 | 0x01, 68 | 0x02, // Destination Address: 2.1.1.2 69 | ]; 70 | let parsed = IPv4 { 71 | version: Version::V4, 72 | internet_header_length: 5, 73 | differentiated_services_code_point: 0, 74 | explicit_congestion_notification: 0, 75 | total_length: 1428, 76 | identification: 0x83f6, 77 | flags: Flags { 78 | reserved: false, 79 | dont_fragment: true, 80 | more_fragments: false, 81 | }, 82 | fragment_offset: 0x0, 83 | time_to_live: 64, 84 | protocol: 1, 85 | header_checksum: 0xeb6e, 86 | source_address: [2, 1, 1, 1], 87 | destination_address: [2, 1, 1, 2], 88 | }; 89 | assert_eq!((parsed, 160), IPv4::decode_bytes(&raw, BigEndian).unwrap()); 90 | assert_eq!(raw, parsed.encode_bytes(BigEndian).unwrap().as_slice()) 91 | } 92 | -------------------------------------------------------------------------------- /bin-proto/tests/skip.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use bin_proto::{BitCodec, BitDecode, BitEncode, Error}; 4 | use bitstream_io::BigEndian; 5 | 6 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 7 | struct SkipEncode { 8 | #[bin_proto(skip_encode)] 9 | a: u8, 10 | b: u8, 11 | } 12 | 13 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 14 | struct SkipDecode { 15 | #[bin_proto(skip_decode)] 16 | a: u8, 17 | b: u8, 18 | } 19 | 20 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 21 | struct Skip { 22 | #[bin_proto(skip)] 23 | a: u8, 24 | b: u8, 25 | } 26 | 27 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 28 | #[bin_proto(discriminant_type = u8)] 29 | enum SkipEncodeEnum { 30 | #[bin_proto(discriminant = 1)] 31 | A, 32 | #[bin_proto(discriminant = 2)] 33 | #[bin_proto(skip_encode)] 34 | B, 35 | } 36 | 37 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 38 | #[bin_proto(discriminant_type = u8)] 39 | enum SkipDecodeEnum { 40 | #[bin_proto(discriminant = 1)] 41 | A, 42 | #[bin_proto(discriminant = 2)] 43 | #[bin_proto(skip_decode)] 44 | #[allow(unused)] 45 | B, 46 | } 47 | 48 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 49 | #[bin_proto(discriminant_type = u8)] 50 | enum SkipEnum { 51 | #[bin_proto(discriminant = 1)] 52 | A, 53 | #[bin_proto(discriminant = 2)] 54 | #[bin_proto(skip)] 55 | B, 56 | } 57 | 58 | #[test] 59 | fn skip_encode_struct() { 60 | let s = SkipEncode { a: 10, b: 20 }; 61 | let bytes = s.encode_bytes(BigEndian).unwrap(); 62 | assert_eq!(bytes, vec![20]); 63 | 64 | assert!(SkipEncode::decode_bytes(&bytes, BigEndian).is_err()); 65 | } 66 | 67 | #[test] 68 | fn skip_decode_struct() { 69 | let s = SkipDecode { a: 10, b: 20 }; 70 | let bytes = s.encode_bytes(BigEndian).unwrap(); 71 | assert_eq!(bytes, vec![10, 20]); 72 | 73 | let (decoded, len) = SkipDecode::decode_bytes(&bytes, BigEndian).unwrap(); 74 | 75 | assert_eq!(decoded, SkipDecode { a: 0, b: 10 }); 76 | assert_eq!(len, 8); 77 | } 78 | 79 | #[test] 80 | fn skip_struct() { 81 | let s = Skip { a: 10, b: 20 }; 82 | let bytes = s.encode_bytes(BigEndian).unwrap(); 83 | assert_eq!(bytes, vec![20]); 84 | 85 | let (decoded, len) = Skip::decode_bytes(&bytes, BigEndian).unwrap(); 86 | 87 | assert_eq!(decoded, Skip { a: 0, b: 20 }); 88 | assert_eq!(len, 8); 89 | } 90 | 91 | #[test] 92 | fn skip_encode_enum() { 93 | let a = SkipEncodeEnum::A; 94 | assert_eq!(a.encode_bytes(BigEndian).unwrap(), vec![1]); 95 | 96 | let b = SkipEncodeEnum::B; 97 | assert!(b.encode_bytes(BigEndian).is_err()); 98 | } 99 | 100 | #[test] 101 | fn skip_decode_enum() { 102 | let (decoded, _) = SkipDecodeEnum::decode_bytes(&[1], BigEndian).unwrap(); 103 | assert_eq!(decoded, SkipDecodeEnum::A); 104 | 105 | let result = SkipDecodeEnum::decode_bytes(&[2], BigEndian); 106 | assert!(matches!(result, Err(Error::Discriminant))); 107 | } 108 | 109 | #[test] 110 | fn skip_enum() { 111 | assert!(SkipEnum::B.encode_bytes(BigEndian).is_err()); 112 | 113 | let result = SkipEnum::decode_bytes(&[2], BigEndian); 114 | assert!(matches!(result, Err(Error::Discriminant))); 115 | } 116 | -------------------------------------------------------------------------------- /bin-proto-derive/src/codegen/trait_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::attr::{Attrs, Ctx}; 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, Result, Token}; 5 | 6 | #[allow(clippy::large_enum_variant)] 7 | pub enum TraitImplType { 8 | Decode, 9 | Encode, 10 | TaggedDecode(syn::Type), 11 | UntaggedEncode, 12 | Discriminable, 13 | } 14 | 15 | pub fn impl_trait_for( 16 | ast: &syn::DeriveInput, 17 | impl_body: &TokenStream, 18 | typ: &TraitImplType, 19 | ) -> Result { 20 | let name = &ast.ident; 21 | let attrs = Attrs::parse(ast.attrs.as_slice(), None, ast.span())?; 22 | 23 | let generics = &ast.generics; 24 | let (_, ty_generics, _) = generics.split_for_impl(); 25 | let mut generics = ast.generics.clone(); 26 | 27 | let mut trait_generics: Punctuated = Punctuated::new(); 28 | 29 | if matches!( 30 | typ, 31 | TraitImplType::Decode 32 | | TraitImplType::Encode 33 | | TraitImplType::TaggedDecode(_) 34 | | TraitImplType::UntaggedEncode 35 | ) { 36 | if let Some(ctx_generics) = attrs.ctx_generics { 37 | generics.params.extend(ctx_generics); 38 | } 39 | 40 | trait_generics.push(if let Some(Ctx::Concrete(ctx)) = attrs.ctx { 41 | quote!(#ctx) 42 | } else { 43 | let ident = syn::Ident::new("__Ctx", Span::call_site()); 44 | let bounds = if let Some(Ctx::Bounds(bounds)) = attrs.ctx { 45 | bounds.into_iter().collect() 46 | } else { 47 | Punctuated::new() 48 | }; 49 | generics 50 | .params 51 | .push(syn::GenericParam::Type(syn::TypeParam { 52 | attrs: Vec::new(), 53 | ident: ident.clone(), 54 | colon_token: None, 55 | bounds, 56 | eq_token: None, 57 | default: None, 58 | })); 59 | quote!(#ident) 60 | }); 61 | } 62 | 63 | let trait_name = match &typ { 64 | TraitImplType::Decode => quote!(BitDecode), 65 | TraitImplType::Encode => quote!(BitEncode), 66 | TraitImplType::UntaggedEncode => { 67 | trait_generics.push(quote!(::bin_proto::Untagged)); 68 | quote!(BitEncode) 69 | } 70 | TraitImplType::TaggedDecode(discriminant) => { 71 | let mut bounds = Punctuated::new(); 72 | bounds.push(parse_quote!(::core::convert::TryInto<#discriminant>)); 73 | generics 74 | .params 75 | .push(syn::GenericParam::Type(syn::TypeParam { 76 | attrs: Vec::new(), 77 | ident: syn::Ident::new("__Tag", Span::call_site()), 78 | colon_token: None, 79 | bounds, 80 | eq_token: None, 81 | default: None, 82 | })); 83 | trait_generics.push(quote!(::bin_proto::Tag<__Tag>)); 84 | quote!(BitDecode) 85 | } 86 | TraitImplType::Discriminable => quote!(Discriminable), 87 | }; 88 | 89 | let (impl_generics, _, where_clause) = generics.split_for_impl(); 90 | Ok(quote!( 91 | #[automatically_derived] 92 | impl #impl_generics ::bin_proto::#trait_name<#trait_generics> for #name #ty_generics 93 | #where_clause { 94 | #impl_body 95 | } 96 | )) 97 | } 98 | -------------------------------------------------------------------------------- /bin-proto/src/impls/tuple.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitRead, BitWrite, Endianness}; 2 | 3 | use crate::{BitDecode, BitEncode, Result}; 4 | 5 | macro_rules! impl_tuple { 6 | ($($idx:tt $t:ident),*) => { 7 | #[cfg_attr(docsrs, doc(hidden))] 8 | impl $crate::BitDecode for ($($t,)*) 9 | where 10 | $($t: $crate::BitDecode,)* 11 | { 12 | fn decode( 13 | read: &mut R, 14 | ctx: &mut Ctx, 15 | (): (), 16 | ) -> $crate::Result 17 | where 18 | R: ::bitstream_io::BitRead, 19 | E: ::bitstream_io::Endianness, 20 | { 21 | Ok(($(<$t as $crate::BitDecode>::decode::<_, E>(read, ctx, ())?,)*)) 22 | } 23 | } 24 | 25 | #[cfg_attr(docsrs, doc(hidden))] 26 | impl $crate::BitEncode for ($($t,)*) 27 | where 28 | $($t: $crate::BitEncode,)* 29 | { 30 | fn encode( 31 | &self, 32 | write: &mut W, 33 | ctx: &mut Ctx, 34 | (): () 35 | ) -> $crate::Result<()> 36 | where 37 | W: ::bitstream_io::BitWrite, 38 | E: ::bitstream_io::Endianness, 39 | { 40 | $( 41 | $crate::BitEncode::encode::<_, E>(&self.$idx, write, ctx, ())?; 42 | )* 43 | Ok(()) 44 | } 45 | } 46 | }; 47 | } 48 | 49 | #[cfg_attr(docsrs, doc(fake_variadic))] 50 | #[cfg_attr( 51 | docsrs, 52 | doc = "This trait is implemented for tuples with up to 16 items." 53 | )] 54 | impl BitDecode for (T,) 55 | where 56 | T: BitDecode, 57 | { 58 | fn decode(read: &mut R, ctx: &mut Ctx, tag: Tag) -> Result 59 | where 60 | R: BitRead, 61 | E: Endianness, 62 | { 63 | Ok((BitDecode::decode::(read, ctx, tag)?,)) 64 | } 65 | } 66 | 67 | #[cfg_attr( 68 | docsrs, 69 | doc = "This trait is implemented for tuples with up to 16 items." 70 | )] 71 | #[cfg_attr(docsrs, doc(fake_variadic))] 72 | impl BitEncode for (T,) 73 | where 74 | T: BitEncode, 75 | { 76 | fn encode(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()> 77 | where 78 | W: BitWrite, 79 | E: Endianness, 80 | { 81 | self.0.encode::(write, ctx, tag) 82 | } 83 | } 84 | 85 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10, 11 T11, 12 T12, 13 T13, 14 T14, 15 T15); 86 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10, 11 T11, 12 T12, 13 T13, 14 T14); 87 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10, 11 T11, 12 T12, 13 T13); 88 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10, 11 T11, 12 T12); 89 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10, 11 T11); 90 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9, 10 T10); 91 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8, 9 T9); 92 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7, 8 T8); 93 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6, 7 T7); 94 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5, 6 T6); 95 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4, 5 T5); 96 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3, 4 T4); 97 | impl_tuple!(0 T0, 1 T1, 2 T2, 3 T3); 98 | impl_tuple!(0 T0, 1 T1, 2 T2); 99 | impl_tuple!(0 T0, 1 T1); 100 | 101 | test_codec!((u8,); (1,) => [0x01]); 102 | test_roundtrip!((u8,)); 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bin-proto 2 | 3 | [![crates](https://img.shields.io/crates/v/bin-proto.svg)](https://crates.io/crates/bin-proto) 4 | [![tests](https://github.com/wojciech-graj/bin-proto/actions/workflows/ci.yml/badge.svg)](https://github.com/wojciech-graj/bin-proto/actions/workflows/ci.yml) 5 | [![docs.rs](https://docs.rs/bin-proto/badge.svg)](https://docs.rs/bin-proto) 6 | ![msrv](https://img.shields.io/crates/msrv/bin-proto) 7 | [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.txt) 8 | 9 | Conversion to/from binary for arbitrary types. With `no_std` and `no_alloc` support. 10 | 11 | This crate adds a trait (and a custom derive for ease-of-use) that can be 12 | implemented on types, allowing structured data to be sent and received from any 13 | binary stream. It is recommended to use 14 | [bitstream_io](https://docs.rs/bitstream-io/latest/bitstream_io/) if you need 15 | bit streams, as their `BitRead` and `BitWrite` traits are being used internally. 16 | 17 | An alternative to [deku](https://crates.io/crates/deku) and [binrw](https://crates.io/crates/binrw). 18 | 19 | ## Example 20 | 21 | Define a type with the `#[derive(bin_proto::BitDecode, bin_proto::BitEncode)]` attributes. 22 | 23 | ```rust 24 | use bin_proto::{BitDecode, BitEncode, BitCodec}; 25 | 26 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 27 | #[bin_proto(discriminant_type = u8)] 28 | #[bin_proto(bits = 4)] 29 | enum E { 30 | V1 = 1, 31 | #[bin_proto(discriminant = 4)] 32 | V4, 33 | } 34 | 35 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 36 | struct S { 37 | #[bin_proto(bits = 1)] 38 | bitflag: bool, 39 | #[bin_proto(bits = 3)] 40 | bitfield: u8, 41 | enum_: E, 42 | #[bin_proto(write_value = self.arr.len() as u8)] 43 | arr_len: u8, 44 | #[bin_proto(tag = arr_len as usize)] 45 | arr: Vec, 46 | #[bin_proto(tag_type = u16, tag_value = self.prefixed_arr.len() as u16)] 47 | prefixed_arr: Vec, 48 | #[bin_proto(untagged)] 49 | read_to_end: Vec, 50 | } 51 | 52 | assert_eq!( 53 | S::decode_bytes(&[ 54 | 0b1000_0000 // bitflag: true (1) 55 | | 0b101_0000 // bitfield: 5 (101) 56 | | 0b0001, // enum_: V1 (0001) 57 | 0x02, // arr_len: 2 58 | 0x21, 0x37, // arr: [0x21, 0x37] 59 | 0x00, 0x01, 0x33, // prefixed_arr: [0x33] 60 | 0x01, 0x02, 0x03, // read_to_end: [0x01, 0x02, 0x03] 61 | ], bin_proto::BigEndian).unwrap().0, 62 | S { 63 | bitflag: true, 64 | bitfield: 5, 65 | enum_: E::V1, 66 | arr_len: 2, 67 | arr: vec![0x21, 0x37], 68 | prefixed_arr: vec![0x33], 69 | read_to_end: vec![0x01, 0x02, 0x03], 70 | } 71 | ); 72 | ``` 73 | 74 | You can implement `BitEncode` and `BitDecode` on your own types, and parse with context: 75 | 76 | ```rust 77 | use bin_proto::{BitDecode, BitEncode}; 78 | 79 | pub struct Ctx; 80 | 81 | pub struct NeedsCtx; 82 | 83 | impl BitDecode for NeedsCtx { 84 | fn decode( 85 | _read: &mut R, 86 | _ctx: &mut Ctx, 87 | _tag: (), 88 | ) -> bin_proto::Result 89 | where 90 | R: bin_proto::BitRead, 91 | E: bin_proto::Endianness, 92 | { 93 | // Use ctx here 94 | Ok(Self) 95 | } 96 | } 97 | 98 | impl BitEncode for NeedsCtx { 99 | fn encode( 100 | &self, 101 | _write: &mut W, 102 | _ctx: &mut Ctx, 103 | _tag: (), 104 | ) -> bin_proto::Result<()> 105 | where 106 | W: bin_proto::BitWrite, 107 | E: bin_proto::Endianness, 108 | { 109 | // Use ctx here 110 | Ok(()) 111 | } 112 | } 113 | 114 | #[derive(BitDecode, BitEncode)] 115 | #[bin_proto(ctx = Ctx)] 116 | pub struct WithCtx(NeedsCtx); 117 | 118 | WithCtx(NeedsCtx) 119 | .encode_bytes_ctx(bin_proto::BigEndian, &mut Ctx, ()) 120 | .unwrap(); 121 | ``` 122 | 123 | ## Performance / Alternatives 124 | 125 | See [GitHub Actions](https://github.com/wojciech-graj/bin-proto/actions) for latest benchmark results with comparison against deku. 126 | -------------------------------------------------------------------------------- /bin-proto/src/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "alloc")] 2 | use alloc::boxed::Box; 3 | use core::{convert::Infallible, fmt}; 4 | #[cfg(feature = "std")] 5 | use std::io; 6 | 7 | #[cfg(not(feature = "std"))] 8 | use core2::io; 9 | 10 | /// Alias for a Result with the error type [`Error`]. 11 | pub type Result = core::result::Result; 12 | 13 | #[derive(Debug)] 14 | #[non_exhaustive] 15 | #[allow(missing_docs)] 16 | pub enum Error { 17 | Io(io::Error), 18 | #[cfg(feature = "alloc")] 19 | FromUtf8(alloc::string::FromUtf8Error), 20 | #[cfg(feature = "alloc")] 21 | Nul(alloc::ffi::NulError), 22 | TryFromInt(core::num::TryFromIntError), 23 | Borrow(core::cell::BorrowError), 24 | Discriminant, 25 | TagConvert, 26 | #[cfg(feature = "std")] 27 | Poison, 28 | Underrun { 29 | read_bits: u64, 30 | available_bits: u64, 31 | }, 32 | EncodeSkipped, 33 | Magic(&'static [u8]), 34 | #[cfg(feature = "alloc")] 35 | /// A catch-all for errors generated by user code 36 | Boxed(Box), 37 | /// A catch-all for error generated by user code, with a static message 38 | Other(&'static str), 39 | } 40 | 41 | impl fmt::Display for Error { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | match self { 44 | Self::Io(e) => write!(f, "{e}"), 45 | #[cfg(feature = "alloc")] 46 | Self::FromUtf8(e) => write!(f, "{e}"), 47 | #[cfg(feature = "alloc")] 48 | Self::Nul(e) => write!(f, "{e}"), 49 | Self::TryFromInt(e) => write!(f, "{e}"), 50 | Self::Borrow(e) => write!(f, "{e}"), 51 | Self::Discriminant => write!(f, "unknown enum discriminant"), 52 | Self::TagConvert => write!(f, "failed to convert tag"), 53 | #[cfg(feature = "std")] 54 | Self::Poison => write!(f, "poisoned lock"), 55 | Self::Magic(expected) => write!(f, "magic mismatch. Expected: {expected:?}."), 56 | Self::Underrun { 57 | read_bits: read, 58 | available_bits: available, 59 | } => { 60 | write!(f, "buffer underrun: read {read} of {available} bits") 61 | } 62 | Self::EncodeSkipped => write!(f, "attempted to encode skipped enum variant"), 63 | #[cfg(feature = "alloc")] 64 | Self::Boxed(e) => write!(f, "{e}"), 65 | Self::Other(e) => write!(f, "other: {e}"), 66 | } 67 | } 68 | } 69 | 70 | impl From for Error { 71 | #[inline] 72 | fn from(value: io::Error) -> Self { 73 | Self::Io(value) 74 | } 75 | } 76 | 77 | #[cfg(feature = "alloc")] 78 | impl From for Error { 79 | #[inline] 80 | fn from(value: alloc::string::FromUtf8Error) -> Self { 81 | Self::FromUtf8(value) 82 | } 83 | } 84 | 85 | #[cfg(feature = "alloc")] 86 | impl From for Error { 87 | #[inline] 88 | fn from(value: alloc::ffi::NulError) -> Self { 89 | Self::Nul(value) 90 | } 91 | } 92 | 93 | impl From for Error { 94 | #[inline] 95 | fn from(value: core::num::TryFromIntError) -> Self { 96 | Self::TryFromInt(value) 97 | } 98 | } 99 | 100 | impl From for Error { 101 | #[inline] 102 | fn from(value: core::cell::BorrowError) -> Self { 103 | Self::Borrow(value) 104 | } 105 | } 106 | 107 | impl From for Error { 108 | #[inline] 109 | fn from(_: Infallible) -> Self { 110 | unreachable!() 111 | } 112 | } 113 | 114 | #[cfg(feature = "std")] 115 | impl From> for Error { 116 | fn from(_: std::sync::PoisonError) -> Self { 117 | Self::Poison 118 | } 119 | } 120 | 121 | impl core::error::Error for Error {} 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use super::*; 126 | 127 | #[allow(unused)] 128 | trait IsSizedSendSync: Sized + Send + Sync {} 129 | 130 | impl IsSizedSendSync for Error {} 131 | } 132 | -------------------------------------------------------------------------------- /bin-proto/tests/ctx.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use std::marker::PhantomData; 4 | 5 | use bin_proto::{BitDecode, BitDecodeExt, BitEncode, BitEncodeExt}; 6 | use bitstream_io::{BigEndian, BitRead, BitWrite, Endianness}; 7 | 8 | #[allow(unused)] 9 | trait Boolean { 10 | fn set(&mut self); 11 | } 12 | 13 | impl Boolean for bool { 14 | fn set(&mut self) { 15 | *self = true 16 | } 17 | } 18 | 19 | #[allow(unused)] 20 | trait TraitWithGeneric<'a, T> 21 | where 22 | T: Boolean, 23 | { 24 | } 25 | 26 | trait CtxTrait { 27 | fn call(&mut self); 28 | } 29 | 30 | #[derive(Debug)] 31 | struct CtxStruct(bool); 32 | 33 | impl CtxTrait for CtxStruct { 34 | fn call(&mut self) { 35 | self.0 = true 36 | } 37 | } 38 | 39 | #[derive(Debug)] 40 | #[allow(unused)] 41 | struct CtxStructWithGenerics<'a, T>(&'a mut T); 42 | 43 | impl<'a, T> CtxTrait for CtxStructWithGenerics<'a, T> 44 | where 45 | T: Boolean, 46 | { 47 | fn call(&mut self) { 48 | self.0.set() 49 | } 50 | } 51 | 52 | #[derive(Debug)] 53 | struct CtxCheck; 54 | 55 | impl BitDecode for CtxCheck { 56 | fn decode(_: &mut R, ctx: &mut Ctx, _: ()) -> Result 57 | where 58 | R: BitRead, 59 | E: Endianness, 60 | { 61 | ctx.call(); 62 | Ok(Self) 63 | } 64 | } 65 | 66 | impl BitEncode for CtxCheck { 67 | fn encode(&self, _: &mut W, ctx: &mut Ctx, (): ()) -> Result<(), bin_proto::Error> 68 | where 69 | W: BitWrite, 70 | E: Endianness, 71 | { 72 | ctx.call(); 73 | Ok(()) 74 | } 75 | } 76 | 77 | #[derive(Debug, BitDecode, BitEncode)] 78 | #[bin_proto(ctx = CtxStruct)] 79 | struct CtxCheckStructWrapper(CtxCheck); 80 | 81 | #[derive(Debug, BitDecode, BitEncode)] 82 | #[bin_proto(ctx = CtxStructWithGenerics<'a, bool>, ctx_generics('a))] 83 | #[allow(unused)] 84 | struct CtxCheckStructWrapperWithGenericsConcreteBool(CtxCheck); 85 | 86 | #[derive(Debug, BitDecode, BitEncode)] 87 | #[bin_proto(ctx = CtxStructWithGenerics<'a, T>, ctx_generics('a, T: Boolean))] 88 | #[allow(unused)] 89 | struct CtxCheckStructWrapperWithGenerics(CtxCheck); 90 | 91 | #[derive(Debug, BitDecode, BitEncode)] 92 | #[bin_proto(ctx_bounds(TraitWithGeneric<'a, bool>, CtxTrait), ctx_generics('a))] 93 | #[allow(unused)] 94 | struct CtxCheckBoundsWithGenericsConcreteBool(CtxCheck); 95 | 96 | #[derive(Debug, BitDecode, BitEncode)] 97 | #[bin_proto(ctx_bounds(TraitWithGeneric<'a, T>, CtxTrait), ctx_generics('a))] 98 | #[allow(unused)] 99 | struct CtxCheckBoundsWithGenerics(CtxCheck, PhantomData); 100 | 101 | #[derive(Debug, BitDecode, BitEncode)] 102 | #[bin_proto(ctx_bounds(CtxTrait))] 103 | struct CtxCheckTraitWrapper(CtxCheck); 104 | 105 | #[test] 106 | fn decode_ctx_passed() { 107 | let mut ctx = CtxStruct(false); 108 | CtxCheck::decode_bytes_ctx(&[], BigEndian, &mut ctx, ()).unwrap(); 109 | assert!(ctx.0); 110 | } 111 | 112 | #[test] 113 | fn encode_ctx_passed() { 114 | let mut ctx = CtxStruct(false); 115 | CtxCheck.encode_bytes_ctx(BigEndian, &mut ctx, ()).unwrap(); 116 | assert!(ctx.0); 117 | } 118 | 119 | #[test] 120 | fn decode_ctx_passed_recur_struct() { 121 | let mut ctx = CtxStruct(false); 122 | CtxCheckStructWrapper(CtxCheck) 123 | .encode_bytes_ctx(BigEndian, &mut ctx, ()) 124 | .unwrap(); 125 | assert!(ctx.0); 126 | } 127 | 128 | #[test] 129 | fn encode_ctx_passed_recur_struct() { 130 | let mut ctx = CtxStruct(false); 131 | CtxCheckStructWrapper(CtxCheck) 132 | .encode_bytes_ctx(BigEndian, &mut ctx, ()) 133 | .unwrap(); 134 | assert!(ctx.0); 135 | } 136 | 137 | #[test] 138 | fn decode_ctx_passed_recur_trait() { 139 | let mut ctx = CtxStruct(false); 140 | CtxCheckTraitWrapper(CtxCheck) 141 | .encode_bytes_ctx(BigEndian, &mut ctx, ()) 142 | .unwrap(); 143 | assert!(ctx.0); 144 | } 145 | 146 | #[test] 147 | fn encode_ctx_passed_recur_trait() { 148 | let mut ctx = CtxStruct(false); 149 | CtxCheckTraitWrapper(CtxCheck) 150 | .encode_bytes_ctx(BigEndian, &mut ctx, ()) 151 | .unwrap(); 152 | assert!(ctx.0); 153 | } 154 | -------------------------------------------------------------------------------- /bin-proto/src/impls/map.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | macro_rules! impl_read_map { 3 | ( 4 | $ty:ident, 6 | $new:expr 7 | ) => { 8 | impl $crate::BitDecode> for $ty 9 | where 10 | K: $crate::BitDecode + $kbound0 + $($kbound1)?, 11 | V: $crate::BitDecode, 12 | Tag: ::core::convert::TryInto, 13 | $($h: $hbound0 + $hbound1)? 14 | { 15 | fn decode( 16 | read: &mut R, 17 | ctx: &mut Ctx, 18 | tag: $crate::Tag, 19 | ) -> $crate::Result 20 | where 21 | R: ::bitstream_io::BitRead, 22 | E: ::bitstream_io::Endianness, 23 | { 24 | let item_count = ::core::convert::TryInto::try_into(tag.0) 25 | .map_err(|_| $crate::Error::TagConvert)?; 26 | let mut this = ($new)(item_count); 27 | for _ in 0..item_count { 28 | this.insert( 29 | $crate::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())?, 30 | $crate::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())? 31 | ); 32 | } 33 | ::core::result::Result::Ok(this) 34 | } 35 | } 36 | 37 | impl $crate::BitDecode for $ty 38 | where 39 | K: $crate::BitDecode + $kbound0 $(+ $kbound1)?, 40 | V: $crate::BitDecode, 41 | $($h: $hbound0 + $hbound1)? 42 | { 43 | fn decode( 44 | read: &mut R, 45 | ctx: &mut Ctx, 46 | _: $crate::Untagged, 47 | ) -> $crate::Result 48 | where 49 | R: ::bitstream_io::BitRead, 50 | E: ::bitstream_io::Endianness, 51 | { 52 | $crate::util::decode_items_to_eof::<_, E, _, _>(read, ctx).collect() 53 | } 54 | } 55 | }; 56 | } 57 | 58 | #[allow(unused)] 59 | macro_rules! impl_write_map { 60 | ( $ty:ident ) => { 61 | impl $crate::BitEncode for $ty 62 | where 63 | K: $crate::BitEncode + $kbound0 $(+ $kbound1)?, 64 | V: $crate::BitEncode 65 | { 66 | fn encode( 67 | &self, 68 | write: &mut W, 69 | ctx: &mut Ctx, 70 | _: $crate::Untagged, 71 | ) -> $crate::Result<()> 72 | where 73 | W: ::bitstream_io::BitWrite, 74 | E: ::bitstream_io::Endianness, 75 | { 76 | for (key, value) in self.iter() { 77 | $crate::BitEncode::encode::<_, E>(key, write, ctx, ())?; 78 | $crate::BitEncode::encode::<_, E>(value, write, ctx, ())?; 79 | } 80 | 81 | Ok(()) 82 | } 83 | } 84 | } 85 | } 86 | 87 | #[cfg(feature = "std")] 88 | mod hash_map { 89 | use core::hash::{BuildHasher, Hash}; 90 | use std::collections::HashMap; 91 | 92 | impl_write_map!(HashMap); 93 | impl_read_map!(HashMap, |n| Self::with_capacity_and_hasher(n, H::default())); 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use crate::{Tag, Untagged}; 98 | 99 | use super::*; 100 | 101 | test_untagged_and_codec!( 102 | HashMap| Untagged, Tag(1); [(1, 2)].into() => [0x01, 0x02] 103 | ); 104 | } 105 | } 106 | 107 | #[cfg(feature = "alloc")] 108 | mod b_tree_map { 109 | use alloc::collections::btree_map::BTreeMap; 110 | 111 | impl_write_map!(BTreeMap); 112 | impl_read_map!(BTreeMap, |_| Self::new()); 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use crate::{Tag, Untagged}; 117 | 118 | use super::*; 119 | 120 | test_untagged_and_codec!( 121 | BTreeMap| Untagged, Tag(3); 122 | [(1, 2), (3, 4), (5, 6)].into() => [0x01, 0x02, 0x03, 0x04, 0x05, 0x06] 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /bin-proto/src/impls/container.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | macro_rules! impl_container_write { 3 | ( 4 | $ty:ident<$($a:lifetime,)? T $(: $tbound0:ident $(+ ?$tbound1:ident + $tbound2:lifetime)?)?> 5 | $(=> $f:ident)? 6 | ) => { 7 | impl<$($a,)? Ctx, Tag, T> $crate::BitEncode for $ty<$($a,)? T> 8 | where 9 | T: $crate::BitEncode $(+ $tbound0 $(+ ?$tbound1 + $tbound2)?)?, 10 | { 11 | fn encode( 12 | &self, 13 | write: &mut W, 14 | ctx: &mut Ctx, 15 | tag: Tag, 16 | ) -> $crate::Result<()> 17 | where 18 | W: ::bitstream_io::BitWrite, 19 | E: ::bitstream_io::Endianness, 20 | { 21 | use core::ops::Deref; 22 | 23 | $crate::BitEncode::encode::<_, E>( 24 | self $(.$f()?)? .deref(), 25 | write, 26 | ctx, 27 | tag, 28 | ) 29 | } 30 | } 31 | }; 32 | } 33 | 34 | #[allow(unused)] 35 | macro_rules! impl_container_read { 36 | ($ty:ident) => { 37 | impl $crate::BitDecode for $ty 38 | where 39 | T: $crate::BitDecode, 40 | { 41 | fn decode(read: &mut R, ctx: &mut Ctx, tag: Tag) -> $crate::Result 42 | where 43 | R: ::bitstream_io::BitRead, 44 | E: ::bitstream_io::Endianness, 45 | { 46 | Ok($ty::new($crate::BitDecode::decode::<_, E>(read, ctx, tag)?)) 47 | } 48 | } 49 | }; 50 | } 51 | 52 | #[cfg(feature = "alloc")] 53 | mod box_ { 54 | use alloc::boxed::Box; 55 | 56 | impl_container_write!(Box); 57 | impl_container_read!(Box); 58 | test_codec!(Box; Box::new(1) => [0x01]); 59 | test_roundtrip!(Box); 60 | } 61 | 62 | #[cfg(feature = "alloc")] 63 | mod rc { 64 | use alloc::rc::Rc; 65 | 66 | impl_container_write!(Rc); 67 | impl_container_read!(Rc); 68 | test_codec!(Rc; Rc::new(1) => [0x01]); 69 | test_roundtrip!(Rc); 70 | } 71 | 72 | #[cfg(feature = "alloc")] 73 | mod arc { 74 | use alloc::sync::Arc; 75 | 76 | impl_container_write!(Arc); 77 | impl_container_read!(Arc); 78 | test_codec!(Arc; Arc::new(1) => [0x01]); 79 | test_roundtrip!(Arc); 80 | } 81 | 82 | #[cfg(feature = "alloc")] 83 | mod cow { 84 | use alloc::borrow::{Cow, ToOwned}; 85 | 86 | impl_container_write!(Cow<'a, T: ToOwned + ?Sized + 'a>); 87 | test_encode!(Cow; Cow::Owned(1) => [0x01]); 88 | } 89 | 90 | mod cell { 91 | use core::cell::Cell; 92 | 93 | use bitstream_io::{BitWrite, Endianness}; 94 | 95 | use crate::{BitEncode, Result}; 96 | 97 | impl BitEncode for Cell 98 | where 99 | T: BitEncode + Copy, 100 | { 101 | fn encode(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()> 102 | where 103 | W: BitWrite, 104 | E: Endianness, 105 | { 106 | self.get().encode::<_, E>(write, ctx, tag) 107 | } 108 | } 109 | 110 | test_encode!(Cell; Cell::new(1) => [0x01]); 111 | } 112 | 113 | #[cfg(feature = "std")] 114 | mod rwlock { 115 | use std::sync::RwLock; 116 | 117 | impl_container_write!(RwLock => read); 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use alloc::vec::Vec; 122 | 123 | use bitstream_io::{BigEndian, BitWriter}; 124 | 125 | use crate::BitEncode; 126 | 127 | use super::*; 128 | 129 | #[test] 130 | fn encode() { 131 | let mut buffer: Vec = Vec::new(); 132 | BitEncode::encode::<_, BigEndian>( 133 | &RwLock::new(1u8), 134 | &mut BitWriter::endian(&mut buffer, BigEndian), 135 | &mut (), 136 | (), 137 | ) 138 | .unwrap(); 139 | assert_eq!([1], *buffer); 140 | } 141 | } 142 | } 143 | 144 | #[cfg(feature = "std")] 145 | mod mutex { 146 | use std::sync::Mutex; 147 | 148 | impl_container_write!(Mutex => lock); 149 | test_encode!(Mutex; Mutex::new(1) => [0x01]); 150 | } 151 | 152 | mod ref_cell { 153 | use core::cell::RefCell; 154 | 155 | impl_container_write!(RefCell => try_borrow); 156 | test_encode!(RefCell; RefCell::new(1) => [0x01]); 157 | } 158 | -------------------------------------------------------------------------------- /bin-proto/tests/enums.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use std::marker::PhantomData; 4 | 5 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 6 | use bitstream_io::BigEndian; 7 | 8 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 9 | #[bin_proto(discriminant_type = u8)] 10 | #[bin_proto(ctx = ())] 11 | pub enum Enum<'a, T: BitDecode + BitEncode> { 12 | #[bin_proto(discriminant = 1)] 13 | Variant1 { 14 | a: T, 15 | len: u8, 16 | #[bin_proto(tag = len as usize)] 17 | arr: Vec, 18 | }, 19 | #[bin_proto(discriminant = 2)] 20 | Variant2(u32, bool, PhantomData<&'a T>), 21 | } 22 | 23 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 24 | #[bin_proto(discriminant_type = u8)] 25 | #[bin_proto(bits = 2)] 26 | pub enum Enum2 { 27 | #[bin_proto(discriminant = 3, other)] 28 | CatchAll(u8), 29 | #[bin_proto(discriminant = 1)] 30 | Variant1(u8), 31 | #[bin_proto(discriminant = 2)] 32 | Variant2(u16), 33 | } 34 | 35 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 36 | pub struct EnumContainer { 37 | e: Enum2, 38 | } 39 | 40 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 41 | pub struct TaggedEnumContainer { 42 | #[bin_proto(tag_type = u16, tag_value = ::bin_proto::Discriminable::discriminant(&self.e).unwrap() as u16)] 43 | e: Enum2, 44 | } 45 | 46 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 47 | pub struct BitFieldTaggedEnumContainer { 48 | #[bin_proto(write_value = ::bin_proto::Discriminable::discriminant(&self.e).unwrap())] 49 | #[bin_proto(bits = 3)] 50 | discriminant: u8, 51 | #[bin_proto(tag = discriminant)] 52 | e: Enum2, 53 | } 54 | 55 | #[test] 56 | fn decode_enum_variant() { 57 | assert_eq!( 58 | ( 59 | Enum::Variant1 { 60 | a: 64u8, 61 | len: 2, 62 | arr: vec![1, 2] 63 | }, 64 | 40 65 | ), 66 | Enum::decode_bytes(&[1, 64, 2, 1, 2], BigEndian).unwrap() 67 | ); 68 | } 69 | 70 | #[test] 71 | fn encode_enum_variant() { 72 | assert_eq!( 73 | Enum::Variant2::(20, true, PhantomData) 74 | .encode_bytes(BigEndian) 75 | .unwrap(), 76 | vec![2, 0, 0, 0, 20, 1] 77 | ); 78 | } 79 | 80 | #[test] 81 | fn decode_enum_variant_in_container() { 82 | assert_eq!( 83 | ( 84 | EnumContainer { 85 | e: Enum2::Variant1(2) 86 | }, 87 | 10 88 | ), 89 | EnumContainer::decode_bytes(&[64, 128], BigEndian).unwrap() 90 | ); 91 | } 92 | 93 | #[test] 94 | fn encode_enum_variant_in_container() { 95 | assert_eq!( 96 | EnumContainer { 97 | e: Enum2::Variant2(511) 98 | } 99 | .encode_bytes(BigEndian) 100 | .unwrap(), 101 | vec![128, 127, 192] 102 | ); 103 | } 104 | 105 | #[test] 106 | fn decode_enum_variant_in_container_tagged() { 107 | assert_eq!( 108 | ( 109 | TaggedEnumContainer { 110 | e: Enum2::Variant1(2) 111 | }, 112 | 24 113 | ), 114 | TaggedEnumContainer::decode_bytes(&[0, 1, 2], BigEndian).unwrap() 115 | ); 116 | } 117 | 118 | #[test] 119 | fn encode_enum_variant_in_container_tagged() { 120 | assert_eq!( 121 | TaggedEnumContainer { 122 | e: Enum2::Variant2(511) 123 | } 124 | .encode_bytes(BigEndian) 125 | .unwrap(), 126 | vec![0, 2, 1, 255,] 127 | ); 128 | } 129 | 130 | #[test] 131 | fn decode_enum_variant_in_container_tagged_bitfield() { 132 | assert_eq!( 133 | ( 134 | BitFieldTaggedEnumContainer { 135 | discriminant: 1, 136 | e: Enum2::Variant1(2) 137 | }, 138 | 11 139 | ), 140 | BitFieldTaggedEnumContainer::decode_bytes(&[32, 64], BigEndian).unwrap() 141 | ); 142 | } 143 | 144 | #[test] 145 | fn encode_enum_variant_in_container_tagged_bitfield() { 146 | assert_eq!( 147 | BitFieldTaggedEnumContainer { 148 | discriminant: 2, 149 | e: Enum2::Variant2(511) 150 | } 151 | .encode_bytes(BigEndian) 152 | .unwrap(), 153 | vec![64, 63, 224] 154 | ); 155 | } 156 | 157 | #[test] 158 | fn encode_enum_variant_catch_all() { 159 | assert_eq!( 160 | Enum2::CatchAll(8).encode_bytes(BigEndian).unwrap(), 161 | vec![194, 0] 162 | ); 163 | } 164 | 165 | #[test] 166 | fn decode_enum_variant_catch_all_discriminant() { 167 | assert_eq!( 168 | (Enum2::CatchAll(8), 10), 169 | Enum2::decode_bytes(&[194, 0], BigEndian).unwrap() 170 | ); 171 | } 172 | 173 | #[test] 174 | fn decode_enum_variant_catch_all() { 175 | assert_eq!( 176 | (Enum2::CatchAll(8), 10), 177 | Enum2::decode_bytes(&[2, 0], BigEndian).unwrap() 178 | ); 179 | } 180 | -------------------------------------------------------------------------------- /bin-proto/tests/structs.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use std::marker::PhantomData; 4 | 5 | use bin_proto::{BitCodec, BitDecode, BitEncode, Error}; 6 | use bitstream_io::BigEndian; 7 | 8 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 9 | pub struct Foobar { 10 | a: u8, 11 | b: u8, 12 | c: u8, 13 | } 14 | 15 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 16 | pub struct BizBong(u8, u8, pub u8); 17 | 18 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 19 | pub struct PartyInTheFront; 20 | 21 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 22 | #[bin_proto(ctx = ())] 23 | pub struct NamedFieldsWithGenerics { 24 | pub value: A, 25 | pub del: D, 26 | } 27 | 28 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 29 | #[bin_proto(ctx = Ctx)] 30 | pub struct UnnamedFieldsWithGenerics< 31 | Ctx, 32 | A: BitDecode + BitEncode, 33 | D: BitDecode + BitEncode, 34 | >(A, D, PhantomData); 35 | 36 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 37 | #[bin_proto(ctx = ())] 38 | pub struct StructWithExistingBoundedGenerics< 39 | A: ::std::fmt::Display + ::std::fmt::Debug + BitDecode + BitEncode, 40 | > { 41 | foo: A, 42 | } 43 | 44 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 45 | pub struct WithDefault { 46 | a: u8, 47 | #[bin_proto(skip_decode)] 48 | b: u8, 49 | } 50 | 51 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 52 | #[bin_proto(pad_before = 1, pad_after = 12)] 53 | pub struct Padded { 54 | a: u8, 55 | #[bin_proto(pad_before = 4, pad_after = 8)] 56 | b: u8, 57 | c: u8, 58 | } 59 | 60 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 61 | #[bin_proto(magic = &[0x09u8])] 62 | pub struct Magic { 63 | a: u8, 64 | #[bin_proto(magic = b"\x01\x02\x03")] 65 | b: u8, 66 | } 67 | 68 | #[test] 69 | fn named_fields_are_correctly_written() { 70 | assert_eq!( 71 | vec![3, '2' as u8, 1], 72 | Foobar { 73 | a: 3, 74 | b: '2' as u8, 75 | c: 1, 76 | } 77 | .encode_bytes(BigEndian) 78 | .unwrap() 79 | ); 80 | } 81 | 82 | #[test] 83 | fn named_fields_are_correctly_decoded() { 84 | assert_eq!( 85 | ( 86 | Foobar { 87 | a: 3, 88 | b: '2' as u8, 89 | c: 1, 90 | }, 91 | 24 92 | ), 93 | Foobar::decode_bytes(&[3, '2' as u8, 1], BigEndian).unwrap() 94 | ); 95 | } 96 | 97 | #[test] 98 | fn unnamed_fields_are_correctly_written() { 99 | assert_eq!( 100 | vec![6, 1, 9], 101 | BizBong(6, 1, 9).encode_bytes(BigEndian).unwrap() 102 | ); 103 | } 104 | 105 | #[test] 106 | fn unnamed_fields_are_correctly_decoded() { 107 | assert_eq!( 108 | (BizBong(3, 1, 7), 24), 109 | BizBong::decode_bytes(&[3, 1, 7], BigEndian).unwrap() 110 | ); 111 | } 112 | 113 | #[test] 114 | fn unit_structs_are_correctly_written() { 115 | assert_eq!(PartyInTheFront.encode_bytes(BigEndian).unwrap(), &[]); 116 | } 117 | 118 | #[test] 119 | fn unit_structs_are_correctly_decoded() { 120 | assert_eq!( 121 | (PartyInTheFront, 0), 122 | PartyInTheFront::decode_bytes(&[], BigEndian).unwrap() 123 | ); 124 | } 125 | 126 | #[test] 127 | fn default_written_correctly() { 128 | assert_eq!( 129 | vec![1, 2], 130 | WithDefault { a: 1, b: 2 }.encode_bytes(BigEndian).unwrap() 131 | ) 132 | } 133 | 134 | #[test] 135 | fn default_read_correctly() { 136 | assert_eq!( 137 | ( 138 | WithDefault { 139 | a: 1, 140 | b: Default::default() 141 | }, 142 | 8 143 | ), 144 | WithDefault::decode_bytes(&[1], BigEndian).unwrap() 145 | ) 146 | } 147 | 148 | #[test] 149 | fn pad_written_correctly() { 150 | assert_eq!( 151 | vec![0, 128, 16, 0, 24, 0, 0], 152 | Padded { a: 1, b: 2, c: 3 }.encode_bytes(BigEndian).unwrap() 153 | ) 154 | } 155 | 156 | #[test] 157 | fn pad_read_correctly() { 158 | assert_eq!( 159 | (Padded { a: 1, b: 2, c: 3 }, 49), 160 | Padded::decode_bytes(&[0, 128, 16, 0, 24, 0, 0], BigEndian).unwrap() 161 | ) 162 | } 163 | 164 | #[test] 165 | fn magic_written_correctly() { 166 | assert_eq!( 167 | vec![9, 4, 1, 2, 3, 5], 168 | Magic { a: 4, b: 5 }.encode_bytes(BigEndian).unwrap() 169 | ) 170 | } 171 | 172 | #[test] 173 | fn magic_read_correctly() { 174 | assert_eq!( 175 | (Magic { a: 4, b: 5 }, 48), 176 | Magic::decode_bytes(&[9, 4, 1, 2, 3, 5], BigEndian).unwrap() 177 | ) 178 | } 179 | 180 | #[test] 181 | fn incorrect_magic_fails() { 182 | assert!(matches!( 183 | &Magic::decode_bytes(&[10, 4, 1, 2, 3, 5], BigEndian), 184 | Err(Error::Magic(&[9])) 185 | )); 186 | } 187 | 188 | #[test] 189 | fn ipv4() { 190 | #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)] 191 | struct IPv4Header { 192 | #[bin_proto(bits = 4)] 193 | version: u8, 194 | } 195 | 196 | assert_eq!( 197 | IPv4Header::decode_bytes(&[0x45], BigEndian).unwrap(), 198 | (IPv4Header { version: 4 }, 4) 199 | ) 200 | } 201 | -------------------------------------------------------------------------------- /bin-proto-derive/src/codegen/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::{attr::Attrs, codegen, enums}; 2 | use proc_macro2::{Span, TokenStream}; 3 | use syn::{parse_quote, Error, Result}; 4 | 5 | pub fn decode_discriminant(attrs: &Attrs) -> TokenStream { 6 | if let Some(bits) = &attrs.bits { 7 | quote!(::bin_proto::BitDecode::decode::<_, __E>( 8 | __io_reader, 9 | __ctx, 10 | ::bin_proto::Bits::<#bits>, 11 | )) 12 | } else { 13 | quote!(::bin_proto::BitDecode::decode::<_, __E>( 14 | __io_reader, 15 | __ctx, 16 | (), 17 | )) 18 | } 19 | } 20 | 21 | pub fn encode_discriminant(attrs: &Attrs) -> TokenStream { 22 | let encode_tag = if let Some(bits) = &attrs.bits { 23 | quote!(::bin_proto::BitEncode::encode::<_, __E>( 24 | &__tag, 25 | __io_writer, 26 | __ctx, 27 | ::bin_proto::Bits::<#bits>, 28 | )) 29 | } else { 30 | quote!(::bin_proto::BitEncode::encode::<_, __E>( 31 | &__tag, 32 | __io_writer, 33 | __ctx, 34 | (), 35 | )) 36 | }; 37 | quote!({ 38 | let __tag = ::discriminant(self).ok_or(::bin_proto::Error::EncodeSkipped)?; 39 | #encode_tag?; 40 | }) 41 | } 42 | 43 | pub fn encode_variant_fields(plan: &enums::Enum) -> Result { 44 | let variant_match_branches = plan 45 | .variants 46 | .iter() 47 | .map(|variant| { 48 | let variant_name = &variant.ident; 49 | let fields_pattern = bind_fields_pattern(variant_name, &variant.fields); 50 | let encodes = if variant.skip_encode { 51 | quote!(return ::core::result::Result::Err(::bin_proto::Error::EncodeSkipped)) 52 | } else { 53 | codegen::encodes(&variant.fields, false)? 54 | }; 55 | 56 | Ok(quote!(Self :: #fields_pattern => { 57 | #encodes 58 | })) 59 | }) 60 | .collect::>>()?; 61 | 62 | Ok(quote!( 63 | match self { 64 | #(#variant_match_branches,)* 65 | } 66 | )) 67 | } 68 | 69 | pub fn variant_discriminant(plan: &enums::Enum) -> Result { 70 | let variant_match_branches = plan 71 | .variants 72 | .iter() 73 | .map(|variant| { 74 | let variant_name = &variant.ident; 75 | let fields_pattern = bind_fields_pattern(variant_name, &variant.fields); 76 | let discriminant_expr = if variant.skip_encode { 77 | quote!(::core::option::Option::None) 78 | } else { 79 | let discriminant = variant 80 | .discriminant_value 81 | .as_ref() 82 | .ok_or_else(|| Error::new(variant.ident.span(), "missing discriminant"))?; 83 | quote!(::core::option::Option::Some(#discriminant)) 84 | }; 85 | 86 | Ok(quote!(Self :: #fields_pattern => { 87 | #discriminant_expr 88 | })) 89 | }) 90 | .collect::>>()?; 91 | Ok(quote!(match self { 92 | #(#variant_match_branches,)* 93 | })) 94 | } 95 | 96 | pub fn decode_variant_fields(plan: &enums::Enum) -> Result { 97 | let discriminant_match_branches = plan 98 | .variants 99 | .iter() 100 | .filter(|variant| !variant.discriminant_other) 101 | .chain( 102 | plan.variants 103 | .iter() 104 | .filter(|variant| variant.discriminant_other), 105 | ) 106 | .filter(|variant| !variant.skip_decode) 107 | .map(|variant| { 108 | let variant_name = &variant.ident; 109 | let discriminant_literal = variant 110 | .discriminant_other 111 | .then(|| parse_quote!(_)) 112 | .or_else(|| variant.discriminant_value.clone()) 113 | .ok_or_else(|| Error::new(variant.ident.span(), "missing discriminant"))?; 114 | let (decoder, initializer) = codegen::decodes(&variant.fields)?; 115 | 116 | Ok(quote!( 117 | #discriminant_literal => { 118 | #decoder 119 | Self::#variant_name #initializer 120 | } 121 | )) 122 | }) 123 | .collect::>>()?; 124 | 125 | let discriminant_ty = &plan.discriminant_ty; 126 | 127 | Ok(quote!( 128 | { 129 | match ::core::convert::TryInto::<#discriminant_ty>::try_into(__tag.0) 130 | .map_err(|_| ::bin_proto::Error::TagConvert)? { 131 | #(#discriminant_match_branches,)* 132 | unknown_discriminant => { 133 | return Err(::bin_proto::Error::Discriminant); 134 | }, 135 | } 136 | } 137 | )) 138 | } 139 | 140 | pub fn bind_fields_pattern(parent_name: &syn::Ident, fields: &syn::Fields) -> TokenStream { 141 | match *fields { 142 | syn::Fields::Named(ref fields_named) => { 143 | let field_name_refs = fields_named 144 | .named 145 | .iter() 146 | .map(|f| &f.ident) 147 | .map(|n| quote!( ref #n )); 148 | quote!( 149 | #parent_name { #( #field_name_refs ),* } 150 | ) 151 | } 152 | syn::Fields::Unnamed(ref fields_unnamed) => { 153 | let binding_names: Vec<_> = (0..fields_unnamed.unnamed.len()) 154 | .map(|i| syn::Ident::new(format!("field_{i}").as_str(), Span::call_site())) 155 | .collect(); 156 | 157 | let field_refs: Vec<_> = binding_names.iter().map(|i| quote!( ref #i )).collect(); 158 | quote!( 159 | #parent_name ( #( #field_refs ),* ) 160 | ) 161 | } 162 | syn::Fields::Unit => quote!(#parent_name), 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /bin-proto/tests/tag.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "derive", feature = "alloc"))] 2 | 3 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 4 | use bitstream_io::BigEndian; 5 | 6 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 7 | pub struct Prefix { 8 | pub reason_length: u8, 9 | } 10 | 11 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 12 | pub struct WithElementsLength { 13 | pub count: u32, 14 | pub foo: bool, 15 | #[bin_proto(tag = count as usize)] 16 | pub data: Vec, 17 | } 18 | 19 | #[derive(BitDecode, Debug, PartialEq, Eq)] 20 | pub struct OptionalWriteValue { 21 | #[bin_proto(tag_type = u8)] 22 | pub data: Vec, 23 | } 24 | 25 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 26 | pub struct WithElementsLengthAuto { 27 | #[bin_proto(write_value = self.data.len() as u32)] 28 | pub count: u32, 29 | pub foo: bool, 30 | #[bin_proto(tag = count as usize)] 31 | pub data: Vec, 32 | } 33 | 34 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 35 | #[bin_proto(discriminant_type = u8)] 36 | pub enum WithElementsLengthAutoEnum { 37 | #[bin_proto(discriminant = 1)] 38 | Variant { 39 | #[bin_proto(write_value = data.len() as u32)] 40 | count: u32, 41 | foo: bool, 42 | #[bin_proto(tag = count as usize)] 43 | data: Vec, 44 | }, 45 | } 46 | 47 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 48 | pub struct Prepended { 49 | #[bin_proto(tag_type = u32, tag_value = self.data.len() as u32)] 50 | pub data: Vec, 51 | } 52 | 53 | #[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)] 54 | pub struct PrependedBits { 55 | #[bin_proto(tag_type = u32, tag_value = self.data.len() as u32, tag_bits = 3)] 56 | pub data: Vec, 57 | } 58 | 59 | #[test] 60 | fn can_decode_length_prefix_3_elements() { 61 | assert_eq!( 62 | ( 63 | WithElementsLength { 64 | count: 3, 65 | foo: true, 66 | data: vec![1, 2, 3], 67 | }, 68 | 136 69 | ), 70 | WithElementsLength::decode_bytes( 71 | &[ 72 | 0, 0, 0, 3, // disjoint length prefix 73 | 1, // boolean true 74 | 0, 0, 0, 1, // 1 75 | 0, 0, 0, 2, // 2 76 | 0, 0, 0, 3 // 3 77 | ], 78 | BigEndian, 79 | ) 80 | .unwrap() 81 | ); 82 | } 83 | 84 | #[test] 85 | fn can_encode_auto_length_prefix_3_elements_enum() { 86 | assert_eq!( 87 | WithElementsLengthAuto { 88 | count: 0, 89 | foo: true, 90 | data: vec![1, 2, 3], 91 | } 92 | .encode_bytes(BigEndian) 93 | .unwrap(), 94 | vec![ 95 | 0, 0, 0, 3, // disjoint length prefix 96 | 1, // boolean true 97 | 0, 0, 0, 1, // 1 98 | 0, 0, 0, 2, // 2 99 | 0, 0, 0, 3 // 3 100 | ], 101 | ); 102 | } 103 | 104 | #[test] 105 | fn can_decode_length_prefix_3_elements_enum() { 106 | assert_eq!( 107 | ( 108 | WithElementsLengthAutoEnum::Variant { 109 | count: 3, 110 | foo: true, 111 | data: vec![1, 2, 3], 112 | }, 113 | 144 114 | ), 115 | WithElementsLengthAutoEnum::decode_bytes( 116 | &[ 117 | 1, // Discriminant 118 | 0, 0, 0, 3, // disjoint length prefix 119 | 1, // boolean true 120 | 0, 0, 0, 1, // 1 121 | 0, 0, 0, 2, // 2 122 | 0, 0, 0, 3 // 3 123 | ], 124 | BigEndian, 125 | ) 126 | .unwrap() 127 | ); 128 | } 129 | 130 | #[test] 131 | fn can_encode_auto_length_prefix_3_elements() { 132 | assert_eq!( 133 | WithElementsLengthAutoEnum::Variant { 134 | count: 0, 135 | foo: true, 136 | data: vec![1, 2, 3], 137 | } 138 | .encode_bytes(BigEndian) 139 | .unwrap(), 140 | vec![ 141 | 1, // Discriminant 142 | 0, 0, 0, 3, // disjoint length prefix 143 | 1, // boolean true 144 | 0, 0, 0, 1, // 1 145 | 0, 0, 0, 2, // 2 146 | 0, 0, 0, 3 // 3 147 | ], 148 | ); 149 | } 150 | 151 | #[test] 152 | fn can_decode_prepended_length_prefix_3_elements() { 153 | assert_eq!( 154 | ( 155 | Prepended { 156 | data: vec![1, 2, 3], 157 | }, 158 | 128 159 | ), 160 | Prepended::decode_bytes( 161 | &[ 162 | 0, 0, 0, 3, // length prefix 163 | 0, 0, 0, 1, // 1 164 | 0, 0, 0, 2, // 2 165 | 0, 0, 0, 3 // 3 166 | ], 167 | BigEndian, 168 | ) 169 | .unwrap() 170 | ); 171 | } 172 | 173 | #[test] 174 | fn can_encode_prepended_length_prefix_3_elements() { 175 | assert_eq!( 176 | Prepended { 177 | data: vec![1, 2, 3], 178 | } 179 | .encode_bytes(BigEndian) 180 | .unwrap(), 181 | vec![ 182 | 0, 0, 0, 3, // disjoint length prefix 183 | 0, 0, 0, 1, // 1 184 | 0, 0, 0, 2, // 2 185 | 0, 0, 0, 3 // 3 186 | ], 187 | ); 188 | } 189 | 190 | #[test] 191 | fn can_decode_prepended_length_prefix_bits() { 192 | assert_eq!( 193 | ( 194 | PrependedBits { 195 | data: vec![1, 2, 3], 196 | }, 197 | 99 198 | ), 199 | PrependedBits::decode_bytes(&[96, 0, 0, 0, 32, 0, 0, 0, 64, 0, 0, 0, 96], BigEndian,) 200 | .unwrap() 201 | ); 202 | } 203 | 204 | #[test] 205 | fn can_encode_prepended_length_prefix_bits() { 206 | assert_eq!( 207 | PrependedBits { 208 | data: vec![1, 2, 3], 209 | } 210 | .encode_bytes(BigEndian) 211 | .unwrap(), 212 | vec![96, 0, 0, 0, 32, 0, 0, 0, 64, 0, 0, 0, 96], 213 | ); 214 | } 215 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.12.2 2 | - Implement `BitEncode<_, Bits>` and `BitDecode<_, Bits` for `u128` and `i128` 3 | # v0.12.1 4 | - Fix no_std + alloc 5 | # v0.12.0 6 | - Rename `#[codec(...)]` to `#[bin_proto(...)]` to avoid potential conflicts with other crates 7 | - Seal `BitDecodeExt`, `BitEncodeExt`, `BitCodec` traits 8 | - Add `#[codec(other)]` attribute 9 | - Replace `#[codec(default)]` with `#[codec(skip_encode)]`, `#[codec(skip_decode)]`, `#[codec(skip)]` 10 | - Modify `Discriminable::discriminant` to return `Option` 11 | - Add `&'static str` field for message to `Error::Other` 12 | - Add `BitDecodeExt::decode_all_bytes_ctx` and `BitCodec::decode_all_bytes` 13 | - Rename `#[codec(flexible_array_member)]` to `#[codec(untagged)]` 14 | - Treat `Ipv4Addr` and `Ipv6Addr` as `u32` and `u128` respectively for codec 15 | - Optimize `String` encode 16 | - Optimize decode for variable-length tagged types 17 | # v0.11.1 18 | - Fix potential memory leak in `BitDecode` implementation for `[T; N]` 19 | # v0.11.0 20 | - Return count of bits read from `BitDecodeExt::decode_bytes_ctx`, `BitCodec::decode_bytes` 21 | - Return count of bytes written from `BitEncodeExt::encode_bytes_ctx_buf`, `BitCodec::encode_bytes_buf` 22 | - Implement `BitDecode` for `Box`, `Box`, `Box<[T]>` 23 | # v0.10.0 24 | - Add support for `no_alloc` 25 | - Fix `no_std` support 26 | - Split `BitEncode`, `BitDecode` traits into `BitEncode`, `BitEncodeExt`, `BitDecode`, `BitDecodeExt` 27 | - Add `BitEncodeExt::encode_bytes_ctx_buf` and `BitCodec::encode_bytes_buf` 28 | - Change `Error::Magic` variant to only contain static data 29 | - Rename `Error::UnknownEnumDiscriminant` to `Error::Discriminant` 30 | - Rename `Error::Other` to `Error::Boxed` 31 | - Add `Error::Other` variant 32 | - Optimize reading `[T; N]` 33 | # v0.9.1 34 | - Add `#[codec(default)]`, `#[codec(pad_before = ...)]`, `#[codec(pad_after = ...)]`, `#[codec(magic = ...)]` 35 | - Add `Error::Magic` variant 36 | # v0.9.0 37 | - Change `Bits(u32)` to `Bits` 38 | - Improve `BitDecode` performance for collections 39 | # v0.8.0 40 | - Add `#[codec(tag_bits = ...)]` attribute 41 | - Update `bitstream_io` to 4.0 42 | # v0.7.1 43 | - Expand dependency version ranges 44 | - Bump bitstream-io to 3.1 45 | # v0.7.0 46 | - Combine `TaggedRead`, `FlexibleArrayMemberRead`, `BitFieldRead`, and `ProtocolRead` traits into `BitDecode` 47 | - Combine `UntaggedWrite`, `BitFieldWrite`, and `ProtocolWrite` traits into `BitEncode` 48 | - Replace `BitRead`, `BitWrite`, and `ByteOrder` traits with `BitRead`, `BitWrite`, `Endianness` from `bitstream_io` 49 | - Rename `ProtocolNoCtx` to `Codec` 50 | - Change all `protocol` attributes to `codec` 51 | - Add `std` feature, support `no_std` 52 | - Implement `BitDecode` and `BitEncode` on tuples with up to 16 items, `NonZeroUX`, `NonZeroIX`, `Wrapping`, `Saturating` 53 | - Implement `BitEncode` on `CStr`, `Cow`, `Cell`, `RwLock`, `Mutex`, `RefCell`, `&T`, `&mut T` 54 | - Implement `BitEncode<_, Untagged>` on `[T]`, `str` 55 | - Implement `BitEncode<_, Bits>` and `BitDecode<_, Bits>` on `NonZeroUX`, `NonZeroIX`, `u64`, `i64` 56 | - Don't use implicit hasher in `HashSet` and `HashMap` impls 57 | - Increase MSRV to 1.83.0 58 | - Remove `BitRead::read_unaryX` and `BitWrite::write_unaryX` 59 | - Remove `Error` suffix from `Error` variants 60 | - Add `Error::Borrow`, `Error::Poison`, `Error::SliceTryFromVec` 61 | - Remove `thiserror` dependency 62 | - Deny `unwrap`, `expect`, and `unsafe` 63 | - Clean up tuple impl documentation with `doc(fake_variadic)` 64 | - Update `bitstream_io` to 3.0 65 | # v0.6.0 66 | - Allow multiple attributes in a single `#[protocol(...)]` 67 | - Require unquoted expressions in attributes 68 | - Use nested metas for all lists in attributes 69 | - Add `#[protocol(ctx_generics(...))]` 70 | - Improve attribute parsing and validation 71 | - Impose `non_exhaustive` on `Error` 72 | # v0.5.0 73 | - Split `Protocol` into `ProtocolRead` and `ProtocolWrite` 74 | - Split `ExternallyLengthPrefixed` into `TaggedRead` and `UntaggedWrite` 75 | - Convert `FlexibleArrayMember` to `FlexibleArrayMemberRead` 76 | - Split `BitField` into `BitFieldWrite` and `BitFieldRead` 77 | - Implement `TaggedRead`, `UntaggedWrite` `FlexibleArrayMemberRead` on all list and map types and `Option` 78 | - Add `Error` variant for failed `TryFrom` conversion for `TaggedRead` tags 79 | - Add generic `Tag` parameter to `TaggedRead` 80 | - Allow for `#[protocol(tag(type = "", write_value = ""))]` attribute macro 81 | - Unimplement `ProtocolRead` and `BitFieldRead` on `Option` 82 | - Create `Discriminable` trait for obtaining `enum` discriminants 83 | - Additionally derive `Discriminable`, `TaggedRead`, `UntaggedWrite` 84 | - Implement number-related traits on `usize` and `isize` 85 | # v0.4.2 86 | - Set MSRV at 1.63.0 87 | # v0.4.1 88 | - Impose `Send + Sync` bounds on `Error::Other` 89 | # v0.4.0 90 | - Delete `EnumExt` 91 | - Bump dependencies, and rust version to 2021 92 | - Make lifetime generics work 93 | - Handle context using generics instead of `Any` 94 | - Improve derive macro hygiene 95 | - Improve derive macro error reporting 96 | # v0.3.4 97 | - Do not trigger https://github.com/rust-lang/rust/issues/120363 with generated code 98 | # v0.3.3 99 | - Add `Other` error type 100 | # v0.3.2 101 | - Use `std::net` instead of `core::net` 102 | # v0.3.1 103 | - Implement `Protocol` on `i128`, `u128`, `PhantomPinned`, `BinaryHeap` 104 | - Fix `length` attribute not working in enum variant 105 | # v0.3.0 106 | - Implement `ExternallyLengthPrefixed` on `HashMap`, `BTreeMap`, `String` 107 | - Do not implement `Protocol` on `char`, `range`, `HashMap`, `BTreeMap` 108 | - Implement protocol on `Ipv4Addr` and `Ipv6Addr`, `(T0, )`, `()`, `Box` 109 | - Rename `Enum` trait to `EnumExt` 110 | - Delete `Settings`, replace with `ByteOrder` 111 | - Clean up `Error` 112 | # v0.2.0 113 | - Add context to all parse functions 114 | - Remove `#[repr(...)]`, instead specify repr in `#[protocol(discriminant = "...")]` 115 | - Remove Hints, LengthPrefixed, etc. 116 | - Add `#[protocol(write_value = "")]` for automatically writing arbitrary element value 117 | - Replace `#[protocol(length_prefix(()))]` with `#[protocol(length = "")]` 118 | - Check attribute applicability in every context 119 | - Require discriminants type to be specified for an enum 120 | -------------------------------------------------------------------------------- /bin-proto/src/impls/list.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | macro_rules! impl_read_list { 3 | ( 4 | $ty:ident, 6 | $new:expr, 7 | $push:ident 8 | ) => { 9 | impl $crate::BitDecode> for $ty 10 | where 11 | T: $crate::BitDecode $(+ $tbound0 $(+ $tbound1)?)?, 12 | Tag: ::core::convert::TryInto, 13 | $($h: $hbound0 + $hbound1)? 14 | { 15 | fn decode( 16 | read: &mut R, 17 | ctx: &mut Ctx, 18 | tag: $crate::Tag, 19 | ) -> $crate::Result 20 | where 21 | R: ::bitstream_io::BitRead, 22 | E: ::bitstream_io::Endianness, 23 | { 24 | let item_count = ::core::convert::TryInto::try_into(tag.0) 25 | .map_err(|_| $crate::Error::TagConvert)?; 26 | let mut this = ($new)(item_count); 27 | for _ in 0..item_count { 28 | this.$push($crate::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())?); 29 | } 30 | ::core::result::Result::Ok(this) 31 | } 32 | } 33 | 34 | impl $crate::BitDecode for $ty 35 | where 36 | T: $crate::BitDecode $(+ $tbound0 $(+ $tbound1)?)?, 37 | $($h: $hbound0 + $hbound1)? 38 | { 39 | fn decode( 40 | read: &mut R, 41 | ctx: &mut Ctx, 42 | _: $crate::Untagged, 43 | ) -> $crate::Result 44 | where 45 | R: ::bitstream_io::BitRead, 46 | E: ::bitstream_io::Endianness, 47 | { 48 | $crate::util::decode_items_to_eof::<_, E, _, _>(read, ctx).collect() 49 | } 50 | } 51 | } 52 | } 53 | 54 | #[allow(unused)] 55 | macro_rules! impl_write_list { 56 | ($ty:ident ) => { 57 | impl $crate::BitEncode for $ty 58 | where 59 | T: $crate::BitEncode $(+ $tbound0 $(+ $tbound1)?)? 60 | { 61 | fn encode(&self, 62 | write: &mut W, 63 | ctx: &mut Ctx, 64 | _: $crate::Untagged, 65 | ) -> $crate::Result<()> 66 | where 67 | W: ::bitstream_io::BitWrite, 68 | E: ::bitstream_io::Endianness, 69 | { 70 | $crate::util::encode_items::<_, E, _, _>(self.iter(), write, ctx) 71 | } 72 | } 73 | } 74 | } 75 | 76 | #[cfg(feature = "alloc")] 77 | mod vec { 78 | use alloc::vec::Vec; 79 | 80 | impl_read_list!(Vec, |n| Self::with_capacity(n), push); 81 | impl_write_list!(Vec); 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use crate::{Tag, Untagged}; 86 | 87 | use super::*; 88 | 89 | test_untagged_and_codec!( 90 | Vec| Untagged, Tag(3); alloc::vec![1, 2, 3] => [0x01, 0x02, 0x03] 91 | ); 92 | } 93 | } 94 | 95 | #[cfg(feature = "alloc")] 96 | mod linked_list { 97 | use alloc::collections::linked_list::LinkedList; 98 | 99 | impl_read_list!(LinkedList, |_| Self::new(), push_back); 100 | impl_write_list!(LinkedList); 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use crate::{Tag, Untagged}; 105 | 106 | use super::*; 107 | 108 | test_untagged_and_codec!( 109 | LinkedList| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03] 110 | ); 111 | } 112 | } 113 | 114 | #[cfg(feature = "alloc")] 115 | mod vec_deque { 116 | use alloc::collections::vec_deque::VecDeque; 117 | 118 | impl_read_list!(VecDeque, |n| Self::with_capacity(n), push_back); 119 | impl_write_list!(VecDeque); 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use crate::{Tag, Untagged}; 124 | 125 | use super::*; 126 | 127 | test_untagged_and_codec!( 128 | VecDeque| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03] 129 | ); 130 | } 131 | } 132 | 133 | #[cfg(feature = "alloc")] 134 | mod b_tree_set { 135 | use alloc::collections::btree_set::BTreeSet; 136 | 137 | impl_read_list!(BTreeSet, |_| Self::new(), insert); 138 | impl_write_list!(BTreeSet); 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use crate::{Tag, Untagged}; 143 | 144 | use super::*; 145 | 146 | test_untagged_and_codec!( 147 | BTreeSet| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03] 148 | ); 149 | } 150 | } 151 | 152 | #[cfg(feature = "alloc")] 153 | mod binary_heap { 154 | use alloc::collections::binary_heap::BinaryHeap; 155 | 156 | impl_read_list!(BinaryHeap, |n| Self::with_capacity(n), push); 157 | impl_write_list!(BinaryHeap); 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use alloc::vec::Vec; 162 | 163 | use bitstream_io::{BigEndian, BitReader}; 164 | 165 | use crate::{BitDecode, Tag, Untagged}; 166 | 167 | use super::*; 168 | 169 | #[test] 170 | fn decode() { 171 | let bytes: &[u8] = &[0x01]; 172 | let exp: BinaryHeap = [1].into(); 173 | let read: BinaryHeap = BitDecode::decode::<_, BigEndian>( 174 | &mut BitReader::endian(bytes, BigEndian), 175 | &mut (), 176 | Tag(1), 177 | ) 178 | .unwrap(); 179 | assert_eq!( 180 | exp.into_iter().collect::>(), 181 | read.into_iter().collect::>() 182 | ); 183 | } 184 | 185 | test_encode!(BinaryHeap| Untagged; [1].into() => [0x01]); 186 | } 187 | } 188 | 189 | #[cfg(feature = "std")] 190 | mod hash_set { 191 | use core::hash::{BuildHasher, Hash}; 192 | use std::collections::HashSet; 193 | 194 | impl_read_list!(HashSet, |n| Self::with_capacity_and_hasher(n, H::default()), insert); 195 | impl_write_list!(HashSet); 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use crate::{Tag, Untagged}; 200 | 201 | use super::*; 202 | 203 | test_untagged_and_codec!(HashSet| Untagged, Tag(1); [1].into() => [0x01]); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /bin-proto-derive/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod enums; 2 | pub mod trait_impl; 3 | 4 | use crate::attr::{AttrKind, Attrs, Tag}; 5 | use proc_macro2::TokenStream; 6 | use syn::{spanned::Spanned, Error, Result}; 7 | 8 | pub fn decodes(fields: &syn::Fields) -> Result<(TokenStream, TokenStream)> { 9 | match fields { 10 | syn::Fields::Named(fields) => decode_named_fields(fields), 11 | syn::Fields::Unnamed(fields) => Ok((TokenStream::new(), decode_unnamed_fields(fields)?)), 12 | syn::Fields::Unit => Ok((TokenStream::new(), TokenStream::new())), 13 | } 14 | } 15 | 16 | pub fn encodes(fields: &syn::Fields, self_prefix: bool) -> Result { 17 | match fields { 18 | syn::Fields::Named(fields) => encode_named_fields(fields, self_prefix), 19 | syn::Fields::Unnamed(fields) => encode_unnamed_fields(fields, self_prefix), 20 | syn::Fields::Unit => Ok(TokenStream::new()), 21 | } 22 | } 23 | 24 | fn decode_named_fields(fields_named: &syn::FieldsNamed) -> Result<(TokenStream, TokenStream)> { 25 | let fields = fields_named 26 | .named 27 | .iter() 28 | .map(|field| { 29 | let field_name = &field.ident; 30 | let field_ty = &field.ty; 31 | 32 | let decode = decode(field)?; 33 | 34 | Ok(quote!( 35 | let #field_name : #field_ty = #decode; 36 | )) 37 | }) 38 | .collect::>>()?; 39 | 40 | let field_initializers: Vec<_> = fields_named 41 | .named 42 | .iter() 43 | .map(|field| { 44 | let field_name = &field.ident; 45 | 46 | quote!(#field_name) 47 | }) 48 | .collect(); 49 | 50 | Ok(( 51 | quote!( #( #fields )* ), 52 | quote!( { #( #field_initializers ),* } ), 53 | )) 54 | } 55 | 56 | pub fn decode_pad(pad: &syn::Expr) -> TokenStream { 57 | quote!(::bin_proto::BitRead::skip(__io_reader, #pad)?;) 58 | } 59 | 60 | fn decode(field: &syn::Field) -> Result { 61 | let attrs = Attrs::parse(field.attrs.as_slice(), Some(AttrKind::Field), field.span())?; 62 | 63 | if attrs.skip_decode { 64 | return Ok(quote!(::core::default::Default::default())); 65 | } 66 | 67 | let pad_before = attrs.pad_before.as_ref().map(decode_pad); 68 | let pad_after = attrs.pad_after.as_ref().map(decode_pad); 69 | let magic = attrs.decode_magic(); 70 | 71 | let decode = if let Some(Tag::Prepend { typ, bits, .. }) = attrs.tag { 72 | let tag = if let Some(bits) = bits { 73 | quote!(::bin_proto::Bits::<#bits>) 74 | } else { 75 | quote!(()) 76 | }; 77 | quote!({ 78 | let __tag: #typ = ::bin_proto::BitDecode::decode::<_, __E>(__io_reader, __ctx, #tag)?; 79 | ::bin_proto::BitDecode::decode::<_, __E>( 80 | __io_reader, 81 | __ctx, 82 | ::bin_proto::Tag(__tag) 83 | )? 84 | }) 85 | } else { 86 | let tag = if let Some(field_width) = attrs.bits { 87 | quote!(::bin_proto::Bits::<#field_width>) 88 | } else if attrs.untagged { 89 | quote!(::bin_proto::Untagged) 90 | } else if let Some(Tag::External(tag)) = attrs.tag { 91 | quote!(::bin_proto::Tag(#tag)) 92 | } else { 93 | quote!(()) 94 | }; 95 | quote!(::bin_proto::BitDecode::decode::<_, __E>(__io_reader, __ctx, #tag)?) 96 | }; 97 | 98 | Ok(quote!({ 99 | #pad_before 100 | #magic 101 | let decoded = #decode; 102 | #pad_after 103 | decoded 104 | })) 105 | } 106 | 107 | pub fn encode_pad(pad: &syn::Expr) -> TokenStream { 108 | quote!(::bin_proto::BitWrite::pad(__io_writer, #pad)?;) 109 | } 110 | 111 | fn encode(field: &syn::Field, field_name: &TokenStream) -> Result { 112 | let attrs = Attrs::parse(field.attrs.as_slice(), Some(AttrKind::Field), field.span())?; 113 | 114 | if attrs.skip_encode { 115 | return Ok(TokenStream::new()); 116 | } 117 | 118 | let pad_before = attrs.pad_before.as_ref().map(encode_pad); 119 | let pad_after = attrs.pad_after.as_ref().map(encode_pad); 120 | let magic = attrs.encode_magic(); 121 | 122 | let field_ref = if let Some(value) = attrs.write_value { 123 | let ty = &field.ty; 124 | quote!(&{ 125 | let value: #ty = {#value}; 126 | value 127 | }) 128 | } else { 129 | field_name.clone() 130 | }; 131 | 132 | let encode = if let Some(Tag::Prepend { 133 | typ, 134 | write_value, 135 | bits, 136 | }) = attrs.tag 137 | { 138 | let Some(write_value) = write_value else { 139 | return Err(Error::new(field.span(), "Tag must specify 'write_value'")); 140 | }; 141 | let tag = if let Some(bits) = bits { 142 | quote!(::bin_proto::Bits::<#bits>) 143 | } else { 144 | quote!(()) 145 | }; 146 | quote!( 147 | { 148 | <#typ as ::bin_proto::BitEncode::<_, _>>::encode::<_, __E>( 149 | &{#write_value}, 150 | __io_writer, 151 | __ctx, 152 | #tag 153 | )?; 154 | ::bin_proto::BitEncode::encode::<_, __E>( 155 | #field_ref, 156 | __io_writer, 157 | __ctx, 158 | ::bin_proto::Untagged 159 | )? 160 | } 161 | ) 162 | } else { 163 | let tag = if let Some(field_width) = attrs.bits { 164 | quote!(::bin_proto::Bits::<#field_width>) 165 | } else if matches!(attrs.tag, Some(Tag::External(_))) || attrs.untagged { 166 | quote!(::bin_proto::Untagged) 167 | } else { 168 | quote!(()) 169 | }; 170 | quote!( 171 | { 172 | ::bin_proto::BitEncode::encode::<_, __E>(#field_ref, __io_writer, __ctx, #tag)? 173 | } 174 | ) 175 | }; 176 | 177 | Ok(quote!( 178 | #pad_before 179 | #magic 180 | #encode; 181 | #pad_after 182 | )) 183 | } 184 | 185 | fn encode_named_fields(fields_named: &syn::FieldsNamed, self_prefix: bool) -> Result { 186 | let field_encoders = fields_named 187 | .named 188 | .iter() 189 | .map(|field| { 190 | let field_name = &field.ident; 191 | encode( 192 | field, 193 | &if self_prefix { 194 | quote!(&self. #field_name) 195 | } else { 196 | quote!(#field_name) 197 | }, 198 | ) 199 | }) 200 | .collect::>>()?; 201 | 202 | Ok(quote!( #( #field_encoders )* )) 203 | } 204 | 205 | fn decode_unnamed_fields(fields_unnamed: &syn::FieldsUnnamed) -> Result { 206 | let field_initializers = fields_unnamed 207 | .unnamed 208 | .iter() 209 | .map(|field| { 210 | let field_ty = &field.ty; 211 | let decode = decode(field)?; 212 | 213 | Ok(quote!( 214 | { 215 | let res: #field_ty = #decode; 216 | res 217 | } 218 | )) 219 | }) 220 | .collect::>>()?; 221 | 222 | Ok(quote!( ( #( #field_initializers ),* ) )) 223 | } 224 | 225 | fn encode_unnamed_fields( 226 | fields_unnamed: &syn::FieldsUnnamed, 227 | self_prefix: bool, 228 | ) -> Result { 229 | let field_encoders: Vec<_> = fields_unnamed 230 | .unnamed 231 | .iter() 232 | .enumerate() 233 | .map(|(field_index, field)| { 234 | let field_index = syn::Index::from(field_index); 235 | encode( 236 | field, 237 | &if self_prefix { 238 | quote!(&self. #field_index) 239 | } else { 240 | format!("field_{}", field_index.index).parse()? 241 | }, 242 | ) 243 | }) 244 | .collect::>>()?; 245 | 246 | Ok(quote!( #( #field_encoders )* )) 247 | } 248 | -------------------------------------------------------------------------------- /bin-proto/src/impls/numerics.rs: -------------------------------------------------------------------------------- 1 | use core::num::{ 2 | NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, 3 | NonZeroU32, NonZeroU64, NonZeroU8, 4 | }; 5 | 6 | use bitstream_io::{BitRead, BitWrite, Endianness}; 7 | 8 | use crate::{BitDecode, BitEncode, Bits, Result}; 9 | 10 | impl BitDecode> for bool { 11 | fn decode(read: &mut R, _: &mut Ctx, _: Bits) -> Result 12 | where 13 | R: BitRead, 14 | E: Endianness, 15 | { 16 | if read.read::()? == 0 { 17 | Ok(false) 18 | } else { 19 | Ok(true) 20 | } 21 | } 22 | } 23 | 24 | impl BitEncode> for bool { 25 | fn encode(&self, write: &mut W, _: &mut Ctx, _: Bits) -> Result<()> 26 | where 27 | W: BitWrite, 28 | E: Endianness, 29 | { 30 | write.write::((*self).into())?; 31 | Ok(()) 32 | } 33 | } 34 | 35 | impl BitDecode for bool { 36 | fn decode(read: &mut R, _: &mut Ctx, (): ()) -> Result 37 | where 38 | R: BitRead, 39 | E: Endianness, 40 | { 41 | if read.read_as_to::()? == 0 { 42 | Ok(false) 43 | } else { 44 | Ok(true) 45 | } 46 | } 47 | } 48 | 49 | impl BitEncode for bool { 50 | fn encode(&self, write: &mut W, _: &mut Ctx, (): ()) -> Result<()> 51 | where 52 | W: BitWrite, 53 | E: Endianness, 54 | { 55 | write.write_as_from::(u8::from(*self))?; 56 | Ok(()) 57 | } 58 | } 59 | 60 | macro_rules! impl_codec_for_numeric_unordered { 61 | ($ty:ty => $data_ty:ty) => { 62 | impl $crate::BitDecode for $ty { 63 | fn decode(read: &mut R, _: &mut Ctx, (): ()) -> $crate::Result 64 | where 65 | R: ::bitstream_io::BitRead, 66 | E: ::bitstream_io::Endianness, 67 | { 68 | Ok(::core::convert::TryInto::try_into( 69 | ::bitstream_io::BitRead::read_as_to::(read)?, 70 | )?) 71 | } 72 | } 73 | 74 | impl $crate::BitEncode for $ty { 75 | fn encode(&self, write: &mut W, _: &mut Ctx, (): ()) -> $crate::Result<()> 76 | where 77 | W: ::bitstream_io::BitWrite, 78 | E: ::bitstream_io::Endianness, 79 | { 80 | ::bitstream_io::BitWrite::write_as_from::( 81 | write, 82 | ::core::convert::TryInto::try_into(*self)?, 83 | )?; 84 | Ok(()) 85 | } 86 | } 87 | }; 88 | } 89 | 90 | macro_rules! impl_codec_for_numeric { 91 | ($ty:ty $(: $thru:ident $fallible:tt)? => $data_ty:ty) => { 92 | impl $crate::BitDecode for $ty { 93 | fn decode( 94 | read: &mut R, 95 | _: &mut Ctx, 96 | (): (), 97 | ) -> $crate::Result 98 | where 99 | R: ::bitstream_io::BitRead, 100 | E: ::bitstream_io::Endianness, 101 | { 102 | Ok(::core::convert::TryInto::try_into( 103 | $(::core::convert::TryInto::<$thru>::try_into)?( 104 | ::bitstream_io::BitRead::read_as_to::(read)? 105 | )$($fallible)?, 106 | )?) 107 | } 108 | } 109 | 110 | impl $crate::BitEncode for $ty { 111 | fn encode( 112 | &self, 113 | write: &mut W, 114 | _: &mut Ctx, 115 | (): (), 116 | ) -> $crate::Result<()> 117 | where 118 | W: ::bitstream_io::BitWrite, 119 | E: ::bitstream_io::Endianness 120 | { 121 | ::bitstream_io::BitWrite::write_as_from::( 122 | write, 123 | ::core::convert::TryInto::try_into( 124 | $(::core::convert::TryInto::<$thru>::try_into)?(*self)$($fallible)?, 125 | )?, 126 | )?; 127 | Ok(()) 128 | } 129 | } 130 | }; 131 | } 132 | 133 | macro_rules! impl_bitfield_for_numeric { 134 | ($ty:ty $(: $thru:ident $fallible:tt)? => $data_ty:ty) => { 135 | impl $crate::BitDecode> for $ty { 136 | fn decode( 137 | read: &mut R, 138 | _: &mut Ctx, 139 | _: $crate::Bits, 140 | ) -> $crate::Result 141 | where 142 | R: ::bitstream_io::BitRead, 143 | E: ::bitstream_io::Endianness, 144 | { 145 | Ok(::core::convert::TryInto::try_into( 146 | $(::core::convert::TryInto::<$thru>::try_into)?(::bitstream_io::BitRead::read::( 147 | read 148 | )$($fallible)?)?, 149 | )?) 150 | } 151 | } 152 | 153 | impl $crate::BitEncode> for $ty { 154 | fn encode( 155 | &self, 156 | write: &mut W, 157 | _: &mut Ctx, 158 | _: $crate::Bits, 159 | ) -> $crate::Result<()> 160 | where 161 | W: ::bitstream_io::BitWrite, 162 | E: ::bitstream_io::Endianness 163 | { 164 | ::bitstream_io::BitWrite::write::( 165 | write, 166 | ::core::convert::TryInto::try_into( 167 | $(::core::convert::TryInto::<$thru>::try_into)?(*self)$($fallible)?, 168 | )?, 169 | )?; 170 | Ok(()) 171 | } 172 | } 173 | }; 174 | } 175 | 176 | impl_codec_for_numeric_unordered!(u8 => u8); 177 | impl_codec_for_numeric_unordered!(i8 => i8); 178 | 179 | impl_codec_for_numeric_unordered!(NonZeroU8 => u8); 180 | impl_codec_for_numeric_unordered!(NonZeroI8 => i8); 181 | 182 | impl_codec_for_numeric!(u16 => u16); 183 | impl_codec_for_numeric!(i16 => i16); 184 | impl_codec_for_numeric!(u32 => u32); 185 | impl_codec_for_numeric!(i32 => i32); 186 | impl_codec_for_numeric!(u64 => u64); 187 | impl_codec_for_numeric!(i64 => i64); 188 | impl_codec_for_numeric!(u128 => u128); 189 | impl_codec_for_numeric!(i128 => i128); 190 | 191 | impl_codec_for_numeric!(NonZeroU16 => u16); 192 | impl_codec_for_numeric!(NonZeroI16 => i16); 193 | impl_codec_for_numeric!(NonZeroU32 => u32); 194 | impl_codec_for_numeric!(NonZeroI32 => i32); 195 | impl_codec_for_numeric!(NonZeroU64 => u64); 196 | impl_codec_for_numeric!(NonZeroI64 => i64); 197 | impl_codec_for_numeric!(NonZeroU128 => u128); 198 | impl_codec_for_numeric!(NonZeroI128 => i128); 199 | 200 | impl_codec_for_numeric!(f32 => f32); 201 | impl_codec_for_numeric!(f64 => f64); 202 | 203 | impl_bitfield_for_numeric!(u8 => u8); 204 | impl_bitfield_for_numeric!(i8 => i8); 205 | impl_bitfield_for_numeric!(u16 => u16); 206 | impl_bitfield_for_numeric!(i16 => i16); 207 | impl_bitfield_for_numeric!(u32 => u32); 208 | impl_bitfield_for_numeric!(i32 => i32); 209 | impl_bitfield_for_numeric!(u64 => u64); 210 | impl_bitfield_for_numeric!(i64 => i64); 211 | impl_bitfield_for_numeric!(u128 => u128); 212 | impl_bitfield_for_numeric!(i128 => i128); 213 | 214 | impl_bitfield_for_numeric!(NonZeroU8 => u8); 215 | impl_bitfield_for_numeric!(NonZeroI8 => i8); 216 | impl_bitfield_for_numeric!(NonZeroU16 => u16); 217 | impl_bitfield_for_numeric!(NonZeroI16 => i16); 218 | impl_bitfield_for_numeric!(NonZeroU32 => u32); 219 | impl_bitfield_for_numeric!(NonZeroI32 => i32); 220 | impl_bitfield_for_numeric!(NonZeroU128 => u128); 221 | impl_bitfield_for_numeric!(NonZeroI128 => i128); 222 | 223 | #[cfg(target_pointer_width = "16")] 224 | mod size { 225 | use core::num::{NonZeroIsize, NonZeroUsize}; 226 | 227 | impl_codec_for_numeric!(usize => u16); 228 | impl_bitfield_for_numeric!(usize => u16); 229 | 230 | impl_codec_for_numeric!(NonZeroUsize: usize? => u16); 231 | impl_bitfield_for_numeric!(NonZeroUsize: usize? => u16); 232 | 233 | impl_codec_for_numeric!(isize => i16); 234 | impl_bitfield_for_numeric!(isize => i16); 235 | 236 | impl_codec_for_numeric!(NonZeroIsize: isize? => i16); 237 | impl_bitfield_for_numeric!(NonZeroIsize: isize? => i16); 238 | } 239 | 240 | #[cfg(target_pointer_width = "32")] 241 | mod size { 242 | use core::num::{NonZeroIsize, NonZeroUsize}; 243 | 244 | impl_codec_for_numeric!(usize => u32); 245 | impl_bitfield_for_numeric!(usize => u32); 246 | 247 | impl_codec_for_numeric!(NonZeroUsize: usize? => u32); 248 | impl_bitfield_for_numeric!(NonZeroUsize: usize? => u32); 249 | 250 | impl_codec_for_numeric!(isize => i32); 251 | impl_bitfield_for_numeric!(isize => i32); 252 | 253 | impl_codec_for_numeric!(NonZeroIsize: isize? => i32); 254 | impl_bitfield_for_numeric!(NonZeroIsize: isize? => i32); 255 | } 256 | 257 | #[cfg(target_pointer_width = "64")] 258 | mod size { 259 | use core::num::{NonZeroIsize, NonZeroUsize}; 260 | 261 | impl_codec_for_numeric!(usize => u64); 262 | impl_bitfield_for_numeric!(usize => u64); 263 | 264 | impl_codec_for_numeric!(NonZeroUsize: usize? => u64); 265 | impl_bitfield_for_numeric!(NonZeroUsize: usize? => u64); 266 | 267 | impl_codec_for_numeric!(isize => i64); 268 | impl_bitfield_for_numeric!(isize => i64); 269 | 270 | impl_codec_for_numeric!(NonZeroIsize: isize? => i64); 271 | impl_bitfield_for_numeric!(NonZeroIsize: isize? => i64); 272 | } 273 | -------------------------------------------------------------------------------- /bin-proto-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::pedantic, 3 | clippy::nursery, 4 | clippy::cargo, 5 | clippy::unwrap_used, 6 | clippy::expect_used, 7 | clippy::suspicious, 8 | clippy::complexity, 9 | clippy::perf, 10 | clippy::style, 11 | unsafe_code 12 | )] 13 | #![allow(clippy::module_name_repetitions, clippy::option_if_let_else)] 14 | 15 | #[macro_use] 16 | extern crate quote; 17 | 18 | mod attr; 19 | mod codegen; 20 | mod enums; 21 | 22 | use attr::{AttrKind, Attrs}; 23 | use codegen::{ 24 | decode_pad, encode_pad, 25 | trait_impl::{impl_trait_for, TraitImplType}, 26 | }; 27 | use proc_macro2::TokenStream; 28 | use syn::{parse_macro_input, spanned::Spanned, Error, Result}; 29 | 30 | use crate::codegen::enums::{decode_discriminant, encode_discriminant, variant_discriminant}; 31 | 32 | #[derive(Clone, Copy)] 33 | enum Operation { 34 | Decode, 35 | Encode, 36 | } 37 | 38 | #[proc_macro_derive(BitDecode, attributes(bin_proto))] 39 | pub fn decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 40 | let ast: syn::DeriveInput = parse_macro_input!(input as syn::DeriveInput); 41 | match impl_codec(&ast, Operation::Decode) { 42 | Ok(tokens) => tokens, 43 | Err(e) => e.to_compile_error(), 44 | } 45 | .into() 46 | } 47 | 48 | #[proc_macro_derive(BitEncode, attributes(bin_proto))] 49 | pub fn encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 50 | let ast: syn::DeriveInput = parse_macro_input!(input as syn::DeriveInput); 51 | match impl_codec(&ast, Operation::Encode) { 52 | Ok(tokens) => tokens, 53 | Err(e) => e.to_compile_error(), 54 | } 55 | .into() 56 | } 57 | 58 | fn impl_codec(ast: &syn::DeriveInput, codec_type: Operation) -> Result { 59 | match ast.data { 60 | syn::Data::Struct(ref s) => impl_for_struct(ast, s, codec_type), 61 | syn::Data::Enum(ref e) => impl_for_enum(ast, e, codec_type), 62 | syn::Data::Union(..) => Err(Error::new( 63 | ast.span(), 64 | "bin-proto traits are not derivable on unions", 65 | )), 66 | } 67 | } 68 | 69 | fn impl_for_struct( 70 | ast: &syn::DeriveInput, 71 | strukt: &syn::DataStruct, 72 | codec_type: Operation, 73 | ) -> Result { 74 | let attrs = Attrs::parse(ast.attrs.as_slice(), Some(AttrKind::Struct), ast.span())?; 75 | 76 | let ctx_ty = attrs.ctx_ty(); 77 | 78 | let (impl_body, trait_type) = match codec_type { 79 | Operation::Decode => { 80 | let (decodes, initializers) = codegen::decodes(&strukt.fields)?; 81 | let pad_before = attrs.pad_before.as_ref().map(decode_pad); 82 | let pad_after = attrs.pad_after.as_ref().map(decode_pad); 83 | let magic = attrs.decode_magic(); 84 | 85 | ( 86 | quote!( 87 | fn decode<__R, __E>( 88 | __io_reader: &mut __R, 89 | __ctx: &mut #ctx_ty, 90 | __tag: (), 91 | ) -> ::bin_proto::Result 92 | where 93 | __R: ::bin_proto::BitRead, 94 | __E: ::bin_proto::Endianness, 95 | { 96 | #pad_before 97 | #magic 98 | #decodes 99 | #pad_after 100 | ::core::result::Result::Ok(Self #initializers) 101 | } 102 | ), 103 | TraitImplType::Decode, 104 | ) 105 | } 106 | Operation::Encode => { 107 | let encodes = codegen::encodes(&strukt.fields, true)?; 108 | let pad_before = attrs.pad_before.as_ref().map(encode_pad); 109 | let pad_after = attrs.pad_after.as_ref().map(encode_pad); 110 | let magic = attrs.encode_magic(); 111 | 112 | ( 113 | quote!( 114 | fn encode<__W, __E>( 115 | &self, 116 | __io_writer: &mut __W, 117 | __ctx: &mut #ctx_ty, 118 | (): (), 119 | ) -> ::bin_proto::Result<()> 120 | where 121 | __W: ::bin_proto::BitWrite, 122 | __E: ::bin_proto::Endianness, 123 | { 124 | #pad_before 125 | #magic 126 | #encodes 127 | #pad_after 128 | ::core::result::Result::Ok(()) 129 | } 130 | ), 131 | TraitImplType::Encode, 132 | ) 133 | } 134 | }; 135 | 136 | impl_trait_for(ast, &impl_body, &trait_type) 137 | } 138 | 139 | #[allow(clippy::too_many_lines)] 140 | fn impl_for_enum( 141 | ast: &syn::DeriveInput, 142 | e: &syn::DataEnum, 143 | codec_type: Operation, 144 | ) -> Result { 145 | let plan = enums::Enum::try_new(ast, e)?; 146 | let attrs = Attrs::parse(ast.attrs.as_slice(), Some(AttrKind::Enum), ast.span())?; 147 | let discriminant_ty = &plan.discriminant_ty; 148 | let ctx_ty = attrs.ctx_ty(); 149 | 150 | Ok(match codec_type { 151 | Operation::Decode => { 152 | let decode_variant = codegen::enums::decode_variant_fields(&plan)?; 153 | let impl_body = quote!( 154 | fn decode<__R, __E>( 155 | __io_reader: &mut __R, 156 | __ctx: &mut #ctx_ty, 157 | __tag: ::bin_proto::Tag<__Tag>, 158 | ) -> ::bin_proto::Result 159 | where 160 | __R: ::bin_proto::BitRead, 161 | __E: ::bin_proto::Endianness, 162 | { 163 | ::core::result::Result::Ok(#decode_variant) 164 | } 165 | ); 166 | let tagged_decode_impl = impl_trait_for( 167 | ast, 168 | &impl_body, 169 | &TraitImplType::TaggedDecode(discriminant_ty.clone()), 170 | )?; 171 | 172 | let decode_discriminant = decode_discriminant(&attrs); 173 | let impl_body = quote!( 174 | fn decode<__R, __E>( 175 | __io_reader: &mut __R, 176 | __ctx: &mut #ctx_ty, 177 | __tag: (), 178 | ) -> ::bin_proto::Result 179 | where 180 | __R: ::bin_proto::BitRead, 181 | __E: ::bin_proto::Endianness, 182 | { 183 | let __tag: #discriminant_ty = #decode_discriminant?; 184 | >>::decode::<_, __E>( 185 | __io_reader, 186 | __ctx, 187 | ::bin_proto::Tag(__tag) 188 | ) 189 | } 190 | ); 191 | let decode_impl = impl_trait_for(ast, &impl_body, &TraitImplType::Decode)?; 192 | 193 | quote!( 194 | #tagged_decode_impl 195 | #decode_impl 196 | ) 197 | } 198 | Operation::Encode => { 199 | let encode_variant = codegen::enums::encode_variant_fields(&plan)?; 200 | let pad_before = attrs.pad_before.as_ref().map(encode_pad); 201 | let pad_after = attrs.pad_after.as_ref().map(encode_pad); 202 | let impl_body = quote!( 203 | fn encode<__W, __E>( 204 | &self, 205 | __io_writer: &mut __W, 206 | __ctx: &mut #ctx_ty, 207 | __tag: ::bin_proto::Untagged, 208 | ) -> ::bin_proto::Result<()> 209 | where 210 | __W: ::bin_proto::BitWrite, 211 | __E: ::bin_proto::Endianness, 212 | { 213 | #pad_before 214 | #encode_variant 215 | #pad_after 216 | ::core::result::Result::Ok(()) 217 | } 218 | ); 219 | let untagged_encode_impl = 220 | impl_trait_for(ast, &impl_body, &TraitImplType::UntaggedEncode)?; 221 | 222 | let variant_discriminant = variant_discriminant(&plan)?; 223 | let impl_body = quote!( 224 | type Discriminant = #discriminant_ty; 225 | 226 | fn discriminant(&self) -> ::core::option::Option { 227 | #variant_discriminant 228 | } 229 | ); 230 | let discriminable_impl = 231 | impl_trait_for(ast, &impl_body, &TraitImplType::Discriminable)?; 232 | 233 | let encode_discriminant = encode_discriminant(&attrs); 234 | let impl_body = quote!( 235 | fn encode<__W, __E>( 236 | &self, 237 | __io_writer: &mut __W, 238 | __ctx: &mut #ctx_ty, 239 | (): (), 240 | ) -> ::bin_proto::Result<()> 241 | where 242 | __W: ::bin_proto::BitWrite, 243 | __E: ::bin_proto::Endianness, 244 | { 245 | #pad_before 246 | #encode_discriminant 247 | let res = >::encode::<_, __E>( 248 | self, 249 | __io_writer, 250 | __ctx, 251 | ::bin_proto::Untagged 252 | )?; 253 | #pad_after 254 | ::core::result::Result::Ok(res) 255 | } 256 | ); 257 | let encode_impl = impl_trait_for(ast, &impl_body, &TraitImplType::Encode)?; 258 | 259 | quote!( 260 | #untagged_encode_impl 261 | #discriminable_impl 262 | #encode_impl 263 | ) 264 | } 265 | }) 266 | } 267 | -------------------------------------------------------------------------------- /bin-proto/src/codec.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "alloc")] 2 | use alloc::vec::Vec; 3 | #[cfg(feature = "std")] 4 | use std::io::{self, Cursor}; 5 | 6 | use bitstream_io::{BitRead, BitReader, BitWrite, BitWriter, Endianness}; 7 | #[cfg(not(feature = "std"))] 8 | use core2::io::{self, Cursor}; 9 | 10 | use crate::{Error, Result}; 11 | 12 | /// A trait for bit-level decoding. 13 | pub trait BitDecode: Sized { 14 | /// Reads self from a stream. 15 | fn decode(read: &mut R, ctx: &mut Ctx, tag: Tag) -> Result 16 | where 17 | R: BitRead, 18 | E: Endianness; 19 | } 20 | 21 | /// Utility functionality for bit-level decoding. 22 | pub trait BitDecodeExt: 23 | BitDecode + bit_decode::Sealed 24 | { 25 | /// Parses a new value from its raw byte representation with provided context and tag. 26 | /// 27 | /// Returns a tuple of the parsed value and the number of bits read. 28 | fn decode_bytes_ctx( 29 | bytes: &[u8], 30 | byte_order: E, 31 | ctx: &mut Ctx, 32 | tag: Tag, 33 | ) -> Result<(Self, u64)> 34 | where 35 | E: Endianness, 36 | { 37 | let mut buffer = BitReader::endian(io::Cursor::new(bytes), byte_order); 38 | let this = Self::decode::<_, E>(&mut buffer, ctx, tag)?; 39 | Ok((this, buffer.position_in_bits()?)) 40 | } 41 | 42 | /// Parses a new value from its raw byte representation with provided context and tag, consuming 43 | /// entire buffer. 44 | fn decode_all_bytes_ctx(bytes: &[u8], byte_order: E, ctx: &mut Ctx, tag: Tag) -> Result 45 | where 46 | E: Endianness, 47 | { 48 | let (decoded, read_bits) = Self::decode_bytes_ctx(bytes, byte_order, ctx, tag)?; 49 | let available_bits = u64::try_from(bytes.len())? * 8; 50 | if read_bits == available_bits { 51 | Ok(decoded) 52 | } else { 53 | Err(Error::Underrun { 54 | read_bits, 55 | available_bits, 56 | }) 57 | } 58 | } 59 | } 60 | 61 | impl BitDecodeExt for T where 62 | T: BitDecode + bit_decode::Sealed 63 | { 64 | } 65 | 66 | /// A trait for bit-level encoding. 67 | pub trait BitEncode { 68 | /// Writes a value to a stream. 69 | fn encode(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()> 70 | where 71 | W: BitWrite, 72 | E: Endianness; 73 | } 74 | 75 | /// Utility functionality for bit-level encoding. 76 | pub trait BitEncodeExt: 77 | BitEncode + bit_encode::Sealed 78 | { 79 | /// Gets the raw bytes of this type with provided context and tag. 80 | #[cfg(feature = "alloc")] 81 | fn encode_bytes_ctx(&self, byte_order: E, ctx: &mut Ctx, tag: Tag) -> Result> 82 | where 83 | E: Endianness, 84 | { 85 | let mut data = Vec::new(); 86 | let mut writer = BitWriter::endian(&mut data, byte_order); 87 | self.encode::<_, E>(&mut writer, ctx, tag)?; 88 | writer.byte_align()?; 89 | 90 | Ok(data) 91 | } 92 | 93 | /// Fills the buffer with the raw bytes of this type with provided context and tag. 94 | /// 95 | /// Returns the number of bytes written. 96 | fn encode_bytes_ctx_buf( 97 | &self, 98 | byte_order: E, 99 | ctx: &mut Ctx, 100 | tag: Tag, 101 | buf: &mut [u8], 102 | ) -> Result 103 | where 104 | E: Endianness, 105 | { 106 | let mut cursor = Cursor::new(buf); 107 | let mut writer = BitWriter::endian(&mut cursor, byte_order); 108 | self.encode::<_, E>(&mut writer, ctx, tag)?; 109 | writer.byte_align()?; 110 | 111 | Ok(cursor.position()) 112 | } 113 | } 114 | 115 | impl BitEncodeExt for T where 116 | T: BitEncode + bit_encode::Sealed 117 | { 118 | } 119 | 120 | /// A trait with helper functions for simple codecs. 121 | pub trait BitCodec: BitDecode + BitEncode + bit_codec::Sealed { 122 | /// Parses a new value from its raw byte representation. 123 | /// 124 | /// Returns a tuple of the parsed value and the number of bits read. 125 | fn decode_bytes(bytes: &[u8], byte_order: E) -> Result<(Self, u64)> 126 | where 127 | E: Endianness, 128 | { 129 | Self::decode_bytes_ctx(bytes, byte_order, &mut (), ()) 130 | } 131 | 132 | /// Parses a new value from its raw byte representation, consuming entire buffer. 133 | fn decode_all_bytes(bytes: &[u8], byte_order: E) -> Result 134 | where 135 | E: Endianness, 136 | { 137 | Self::decode_all_bytes_ctx(bytes, byte_order, &mut (), ()) 138 | } 139 | 140 | /// Gets the raw bytes of this type. 141 | #[cfg(feature = "alloc")] 142 | fn encode_bytes(&self, byte_order: E) -> Result> 143 | where 144 | E: Endianness, 145 | { 146 | self.encode_bytes_ctx(byte_order, &mut (), ()) 147 | } 148 | 149 | /// Fills the buffer with the raw bytes of this type. 150 | /// 151 | /// Returns the number of bytes written. 152 | fn encode_bytes_buf(&self, byte_order: E, buf: &mut [u8]) -> Result 153 | where 154 | E: Endianness, 155 | { 156 | self.encode_bytes_ctx_buf(byte_order, &mut (), (), buf) 157 | } 158 | } 159 | 160 | impl BitCodec for T where T: BitDecode + BitEncode + bit_codec::Sealed {} 161 | 162 | mod bit_encode { 163 | use super::BitEncode; 164 | 165 | pub trait Sealed {} 166 | 167 | impl Sealed for T where T: BitEncode {} 168 | } 169 | 170 | mod bit_decode { 171 | use super::BitDecode; 172 | 173 | pub trait Sealed {} 174 | 175 | impl Sealed for T where T: BitDecode {} 176 | } 177 | 178 | mod bit_codec { 179 | use super::{BitDecode, BitEncode}; 180 | 181 | pub trait Sealed {} 182 | 183 | impl Sealed for T where T: BitDecode + BitEncode {} 184 | } 185 | 186 | macro_rules! test_decode { 187 | ($ty:ty | $tag:expr; $bytes:expr => $exp:expr) => { 188 | #[cfg(test)] 189 | #[test] 190 | fn decode() { 191 | let bytes: &[u8] = &$bytes; 192 | let exp: $ty = $exp; 193 | let read: $ty = $crate::BitDecode::<(), _>::decode::<_, ::bitstream_io::BigEndian>( 194 | &mut ::bitstream_io::BitReader::endian(bytes, ::bitstream_io::BigEndian), 195 | &mut (), 196 | $tag, 197 | ) 198 | .unwrap(); 199 | assert_eq!(exp, read); 200 | } 201 | }; 202 | ($ty:ty; $bytes:expr => $exp:expr) => { 203 | #[cfg(test)] 204 | #[test] 205 | fn decode() { 206 | let bytes: &[u8] = &$bytes; 207 | let exp: $ty = $exp; 208 | let decoded: $ty = $crate::BitDecode::decode::<_, ::bitstream_io::BigEndian>( 209 | &mut ::bitstream_io::BitReader::endian(bytes, ::bitstream_io::BigEndian), 210 | &mut (), 211 | (), 212 | ) 213 | .unwrap(); 214 | assert_eq!(exp, decoded); 215 | } 216 | }; 217 | } 218 | 219 | macro_rules! test_encode { 220 | ($ty:ty $(| $tag:expr)?; $value:expr => $exp:expr) => { 221 | #[cfg(test)] 222 | #[test] 223 | fn encode() { 224 | use $crate::BitEncode; 225 | 226 | let exp: &[u8] = &$exp; 227 | let value: $ty = $value; 228 | 229 | #[cfg(feature = "alloc")] 230 | { 231 | let mut buffer: ::alloc::vec::Vec = ::alloc::vec::Vec::new(); 232 | value 233 | .encode::<_, ::bitstream_io::BigEndian>( 234 | &mut ::bitstream_io::BitWriter::endian(&mut buffer, ::bitstream_io::BigEndian), 235 | &mut (), 236 | ($($tag)?), 237 | ) 238 | .unwrap(); 239 | assert_eq!(exp, &buffer); 240 | } 241 | 242 | #[cfg(not(feature = "alloc"))] 243 | { 244 | let mut buffer = [0u8; 16]; 245 | value 246 | .encode::<_, ::bitstream_io::BigEndian>( 247 | &mut ::bitstream_io::BitWriter::endian(&mut ::core2::io::Cursor::new(buffer.as_mut_slice()), ::bitstream_io::BigEndian), 248 | &mut (), 249 | ($($tag)?), 250 | ) 251 | .unwrap(); 252 | assert_eq!(exp, &buffer[..exp.len()]); 253 | assert!(::core::iter::Iterator::all( 254 | &mut ::core::iter::IntoIterator::into_iter(&buffer[exp.len()..]), 255 | |x| *x == 0) 256 | ); 257 | } 258 | } 259 | }; 260 | } 261 | 262 | macro_rules! test_codec { 263 | ($ty:ty$(| $tag_write:expr, $tag_read:expr)?; $value:expr => $bytes:expr) => { 264 | test_decode!($ty$(| $tag_read)?; $bytes => $value); 265 | test_encode!($ty$(| $tag_write)?; $value => $bytes); 266 | } 267 | } 268 | 269 | macro_rules! test_roundtrip { 270 | ($ty:ty) => { 271 | #[cfg(all(test, feature = "alloc"))] 272 | ::proptest::proptest!( 273 | #[test] 274 | fn roundtrip(x in ::proptest::arbitrary::any::<$ty>()) { 275 | let encoded = $crate::BitEncodeExt::encode_bytes_ctx(&x, ::bitstream_io::BigEndian, &mut (), ()).unwrap(); 276 | let decoded = <$ty as $crate::BitDecodeExt>::decode_bytes_ctx(&encoded, ::bitstream_io::BigEndian, &mut (), ()).unwrap().0; 277 | ::proptest::prop_assert_eq!(x, decoded); 278 | } 279 | ); 280 | } 281 | } 282 | 283 | #[allow(unused)] 284 | macro_rules! test_untagged_and_codec { 285 | ($ty:ty | $tag_write:expr, $tag_read:expr; $value:expr => $bytes:expr) => { 286 | test_codec!($ty | $tag_write, $tag_read; $value => $bytes); 287 | #[cfg(test)] 288 | mod untagged { 289 | use super::*; 290 | 291 | test_decode!($ty| $crate::Untagged; $bytes => $value); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /bin-proto-derive/src/attr.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use std::fmt; 3 | use syn::{parenthesized, punctuated::Punctuated, Error, Result, Token}; 4 | 5 | #[allow(clippy::struct_excessive_bools)] 6 | #[derive(Default)] 7 | pub struct Attrs { 8 | pub bits: Option, 9 | pub ctx: Option, 10 | pub ctx_generics: Option>, 11 | pub skip_encode: bool, 12 | pub skip_decode: bool, 13 | pub discriminant: Option, 14 | pub discriminant_type: Option, 15 | pub untagged: bool, 16 | pub magic: Option, 17 | pub pad_after: Option, 18 | pub pad_before: Option, 19 | pub tag: Option, 20 | pub write_value: Option, 21 | pub other: bool, 22 | } 23 | 24 | pub enum Ctx { 25 | Concrete(syn::Type), 26 | Bounds(Vec), 27 | } 28 | 29 | #[allow(clippy::large_enum_variant)] 30 | pub enum Tag { 31 | External(syn::Expr), 32 | Prepend { 33 | typ: syn::Type, 34 | write_value: Option, 35 | bits: Option, 36 | }, 37 | } 38 | 39 | #[derive(Clone, Copy, PartialEq, Eq)] 40 | pub enum AttrKind { 41 | Enum, 42 | Struct, 43 | Variant, 44 | Field, 45 | } 46 | 47 | impl fmt::Display for AttrKind { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | match self { 50 | Self::Enum => write!(f, "enum"), 51 | Self::Struct => write!(f, "struct"), 52 | Self::Variant => write!(f, "variant"), 53 | Self::Field => write!(f, "field"), 54 | } 55 | } 56 | } 57 | 58 | macro_rules! expect_attr_kind { 59 | ($pat:pat, $kind:expr, $meta:expr) => { 60 | if let Some(kind) = $kind { 61 | if !matches!(kind, $pat) { 62 | return Err($meta.error(format!( 63 | "attribute '{}' cannot be applied to {}", 64 | $meta.path.require_ident()?, 65 | kind 66 | ))); 67 | } 68 | } 69 | }; 70 | } 71 | 72 | impl Attrs { 73 | pub fn ctx_ty(&self) -> TokenStream { 74 | if let Some(Ctx::Concrete(ctx)) = &self.ctx { 75 | quote!(#ctx) 76 | } else { 77 | quote!(__Ctx) 78 | } 79 | } 80 | 81 | pub fn decode_magic(&self) -> TokenStream { 82 | if let Some(magic) = &self.magic { 83 | quote!( 84 | let magic: [u8; (#magic).len()] = ::bin_proto::BitDecode::decode::<_, __E>( 85 | __io_reader, 86 | __ctx, 87 | () 88 | )?; 89 | if magic != *(#magic) { 90 | return ::core::result::Result::Err(::bin_proto::Error::Magic(#magic)); 91 | } 92 | ) 93 | } else { 94 | TokenStream::new() 95 | } 96 | } 97 | 98 | pub fn encode_magic(&self) -> TokenStream { 99 | if let Some(magic) = &self.magic { 100 | quote!(::bin_proto::BitEncode::encode::<_, __E>(#magic, __io_writer, __ctx, ())?;) 101 | } else { 102 | TokenStream::new() 103 | } 104 | } 105 | 106 | #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] 107 | pub fn parse(attribs: &[syn::Attribute], kind: Option, span: Span) -> Result { 108 | let mut attrs = Self::default(); 109 | 110 | let mut tag = None; 111 | let mut tag_type = None; 112 | let mut tag_value = None; 113 | let mut tag_bits = None; 114 | 115 | let mut ctx = None; 116 | let mut ctx_bounds = None; 117 | 118 | for attr in attribs { 119 | if !attr.path().is_ident("bin_proto") { 120 | continue; 121 | } 122 | 123 | attr.parse_nested_meta(|meta| { 124 | let Some(ident) = meta.path.get_ident() else { 125 | return Err(meta.error("unrecognized attribute")); 126 | }; 127 | 128 | match ident.to_string().as_str() { 129 | "untagged" => { 130 | expect_attr_kind!(AttrKind::Field, kind, meta); 131 | attrs.untagged = true; 132 | } 133 | "discriminant_type" => { 134 | expect_attr_kind!(AttrKind::Enum, kind, meta); 135 | attrs.discriminant_type = Some(meta.value()?.parse()?); 136 | } 137 | "discriminant" => { 138 | expect_attr_kind!(AttrKind::Variant, kind, meta); 139 | attrs.discriminant = Some(meta.value()?.parse()?); 140 | } 141 | "ctx" => { 142 | expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta); 143 | ctx = Some(meta.value()?.parse()?); 144 | } 145 | "ctx_generics" => { 146 | expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta); 147 | let content; 148 | parenthesized!(content in meta.input); 149 | attrs.ctx_generics = Some( 150 | Punctuated::::parse_separated_nonempty( 151 | &content, 152 | )? 153 | .into_iter() 154 | .collect(), 155 | ); 156 | } 157 | "ctx_bounds" => { 158 | expect_attr_kind!(AttrKind::Enum | AttrKind::Struct, kind, meta); 159 | let content; 160 | parenthesized!(content in meta.input); 161 | ctx_bounds = Some( 162 | Punctuated::::parse_separated_nonempty( 163 | &content, 164 | )? 165 | .into_iter() 166 | .collect(), 167 | ); 168 | } 169 | "bits" => { 170 | expect_attr_kind!(AttrKind::Enum | AttrKind::Field, kind, meta); 171 | attrs.bits = Some(meta.value()?.parse()?); 172 | } 173 | "write_value" => { 174 | expect_attr_kind!(AttrKind::Field, kind, meta); 175 | attrs.write_value = Some(meta.value()?.parse()?); 176 | } 177 | "tag" => { 178 | expect_attr_kind!(AttrKind::Field, kind, meta); 179 | tag = Some(meta.value()?.parse()?); 180 | } 181 | "tag_type" => { 182 | expect_attr_kind!(AttrKind::Field, kind, meta); 183 | tag_type = Some(meta.value()?.parse()?); 184 | } 185 | "tag_value" => { 186 | expect_attr_kind!(AttrKind::Field, kind, meta); 187 | tag_value = Some(meta.value()?.parse()?); 188 | } 189 | "tag_bits" => { 190 | expect_attr_kind!(AttrKind::Field, kind, meta); 191 | tag_bits = Some(meta.value()?.parse()?); 192 | } 193 | "skip_encode" => { 194 | expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta); 195 | attrs.skip_encode = true; 196 | } 197 | "skip_decode" => { 198 | expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta); 199 | attrs.skip_decode = true; 200 | } 201 | "skip" => { 202 | expect_attr_kind!(AttrKind::Field | AttrKind::Variant, kind, meta); 203 | attrs.skip_encode = true; 204 | attrs.skip_decode = true; 205 | } 206 | "pad_before" => { 207 | expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta); 208 | attrs.pad_before = Some(meta.value()?.parse()?); 209 | } 210 | "pad_after" => { 211 | expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta); 212 | attrs.pad_after = Some(meta.value()?.parse()?); 213 | } 214 | "magic" => { 215 | expect_attr_kind!(AttrKind::Struct | AttrKind::Field, kind, meta); 216 | attrs.magic = Some(meta.value()?.parse()?); 217 | } 218 | "other" => { 219 | expect_attr_kind!(AttrKind::Variant, kind, meta); 220 | attrs.other = true; 221 | } 222 | _ => { 223 | return Err(meta.error("unrecognized attribute")); 224 | } 225 | } 226 | 227 | Ok(()) 228 | })?; 229 | } 230 | 231 | match (tag, tag_type, tag_value, tag_bits) { 232 | (Some(tag), None, None, None) => attrs.tag = Some(Tag::External(tag)), 233 | (None, Some(tag_type), tag_value, tag_bits) => { 234 | attrs.tag = Some(Tag::Prepend { 235 | typ: tag_type, 236 | write_value: tag_value, 237 | bits: tag_bits, 238 | }); 239 | } 240 | (None, None, None, None) => {} 241 | _ => { 242 | return Err(Error::new( 243 | span, 244 | "invalid configuration of 'tag', 'tag_type', or 'tag_value' attributes.", 245 | )); 246 | } 247 | } 248 | 249 | match (ctx, ctx_bounds) { 250 | (Some(ctx), None) => attrs.ctx = Some(Ctx::Concrete(ctx)), 251 | (None, Some(ctx_bounds)) => attrs.ctx = Some(Ctx::Bounds(ctx_bounds)), 252 | (None, None) => {} 253 | _ => { 254 | return Err(Error::new( 255 | span, 256 | "use of mutually exclusive 'ctx' and 'ctx_bounds' attributes.", 257 | )); 258 | } 259 | } 260 | 261 | if [attrs.bits.is_some(), attrs.untagged, attrs.tag.is_some()] 262 | .iter() 263 | .filter(|b| **b) 264 | .count() 265 | > 1 266 | { 267 | return Err(Error::new( 268 | span, 269 | "bits, untagged, and tag are mutually-exclusive attributes", 270 | )); 271 | } 272 | 273 | Ok(attrs) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /bench/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | 4 | mod vec { 5 | use test::{black_box, Bencher}; 6 | 7 | mod bench_bin_proto { 8 | use super::*; 9 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 10 | 11 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 12 | struct V { 13 | #[bin_proto(write_value = self.data.len() as u8)] 14 | count: u8, 15 | #[bin_proto(tag = count as usize)] 16 | data: Vec, 17 | } 18 | 19 | #[bench] 20 | fn bench_write(b: &mut Bencher) { 21 | b.iter(|| { 22 | black_box( 23 | V { 24 | count: 255, 25 | data: (0..255).collect(), 26 | } 27 | .encode_bytes(bin_proto::BigEndian), 28 | ) 29 | .unwrap(); 30 | }); 31 | } 32 | 33 | #[bench] 34 | fn bench_read(b: &mut Bencher) { 35 | let mut v = vec![255u8]; 36 | v.extend((0..255).collect::>()); 37 | b.iter(|| { 38 | black_box(V::decode_bytes(v.as_slice(), bin_proto::BigEndian)).unwrap(); 39 | }) 40 | } 41 | } 42 | 43 | mod bench_deku { 44 | use super::*; 45 | use deku::prelude::*; 46 | 47 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 48 | struct V { 49 | #[deku(update = "self.data.len()")] 50 | count: u8, 51 | #[deku(count = "count")] 52 | data: Vec, 53 | } 54 | 55 | #[bench] 56 | fn bench_write(b: &mut Bencher) { 57 | b.iter(|| { 58 | black_box( 59 | V { 60 | count: 255, 61 | data: (0..255).collect(), 62 | } 63 | .to_bytes(), 64 | ) 65 | .unwrap(); 66 | }); 67 | } 68 | 69 | #[bench] 70 | fn bench_read(b: &mut Bencher) { 71 | let mut v = vec![255u8]; 72 | v.extend((0..255).collect::>()); 73 | b.iter(|| { 74 | black_box(V::from_bytes((v.as_slice(), 0))).unwrap(); 75 | }); 76 | } 77 | } 78 | } 79 | 80 | mod enum_ { 81 | use test::{black_box, Bencher}; 82 | 83 | mod bench_bin_proto { 84 | use super::*; 85 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 86 | 87 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 88 | #[bin_proto(discriminant_type = u8)] 89 | enum E { 90 | V0 = 0, 91 | V1 = 1, 92 | V2 = 2, 93 | V3 = 3, 94 | } 95 | 96 | #[bench] 97 | fn bench_enum_write(b: &mut Bencher) { 98 | b.iter(|| { 99 | black_box({ 100 | E::V0.encode_bytes(bin_proto::BigEndian).unwrap(); 101 | E::V1.encode_bytes(bin_proto::BigEndian).unwrap(); 102 | E::V2.encode_bytes(bin_proto::BigEndian).unwrap(); 103 | E::V3.encode_bytes(bin_proto::BigEndian).unwrap(); 104 | }) 105 | }); 106 | } 107 | 108 | #[bench] 109 | fn bench_enum_read(b: &mut Bencher) { 110 | b.iter(|| { 111 | black_box(for i in 0..4 { 112 | E::decode_bytes(&[i], bin_proto::BigEndian).unwrap(); 113 | }) 114 | }); 115 | } 116 | } 117 | 118 | mod bench_deku { 119 | use super::*; 120 | use deku::prelude::*; 121 | 122 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 123 | #[deku(id_type = "u8")] 124 | enum E { 125 | #[deku(id = "0")] 126 | V0, 127 | #[deku(id = "1")] 128 | V1, 129 | #[deku(id = "2")] 130 | V2, 131 | #[deku(id = "3")] 132 | V3, 133 | } 134 | 135 | #[bench] 136 | fn bench_write(b: &mut Bencher) { 137 | b.iter(|| { 138 | black_box({ 139 | E::V0.to_bytes().unwrap(); 140 | E::V1.to_bytes().unwrap(); 141 | E::V2.to_bytes().unwrap(); 142 | E::V3.to_bytes().unwrap(); 143 | }) 144 | }); 145 | } 146 | 147 | #[bench] 148 | fn bench_read(b: &mut Bencher) { 149 | b.iter(|| { 150 | black_box(for i in 0..4 { 151 | E::from_bytes((&[i], 0)).unwrap(); 152 | }) 153 | }); 154 | } 155 | } 156 | } 157 | 158 | mod ipv4 { 159 | use std::net::Ipv4Addr; 160 | use test::{black_box, Bencher}; 161 | 162 | mod bench_bin_proto { 163 | use super::*; 164 | use bin_proto::{BitCodec, BitDecode, BitEncode}; 165 | 166 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 167 | #[bin_proto(discriminant_type = u8)] 168 | #[bin_proto(bits = 4)] 169 | enum Version { 170 | V4 = 4, 171 | } 172 | 173 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 174 | struct Flags { 175 | #[bin_proto(bits = 1)] 176 | reserved: bool, 177 | #[bin_proto(bits = 1)] 178 | dont_fragment: bool, 179 | #[bin_proto(bits = 1)] 180 | more_fragments: bool, 181 | } 182 | 183 | #[derive(Debug, BitDecode, BitEncode, PartialEq)] 184 | struct IPv4 { 185 | version: Version, 186 | #[bin_proto(bits = 4)] 187 | internet_header_length: u8, 188 | #[bin_proto(bits = 6)] 189 | differentiated_services_code_point: u8, 190 | #[bin_proto(bits = 2)] 191 | explicit_congestion_notification: u8, 192 | total_length: u16, 193 | identification: u16, 194 | flags: Flags, 195 | #[bin_proto(bits = 13)] 196 | fragment_offset: u16, 197 | time_to_live: u8, 198 | protocol: u8, 199 | header_checksum: u16, 200 | source_address: Ipv4Addr, 201 | destination_address: Ipv4Addr, 202 | } 203 | 204 | #[bench] 205 | fn bench_ipv4_write(b: &mut Bencher) { 206 | b.iter(|| { 207 | black_box( 208 | IPv4 { 209 | version: Version::V4, 210 | internet_header_length: 5, 211 | differentiated_services_code_point: 0, 212 | explicit_congestion_notification: 0, 213 | total_length: 1428, 214 | identification: 0x83f6, 215 | flags: Flags { 216 | reserved: false, 217 | dont_fragment: true, 218 | more_fragments: false, 219 | }, 220 | fragment_offset: 0x0, 221 | time_to_live: 64, 222 | protocol: 1, 223 | header_checksum: 0xeeee, 224 | source_address: Ipv4Addr::new(2, 1, 1, 1), 225 | destination_address: Ipv4Addr::new(2, 1, 1, 2), 226 | } 227 | .encode_bytes(bin_proto::BigEndian), 228 | ) 229 | .unwrap(); 230 | }); 231 | } 232 | 233 | #[bench] 234 | fn bench_ipv4_read(b: &mut Bencher) { 235 | b.iter(|| { 236 | black_box(IPv4::decode_bytes( 237 | &[ 238 | 0b0100_0000 // Version: 4 239 | | 0b0101, // Header Length: 5, 240 | 0x00, // Differentiated Services Codepoint: 0, Explicit Congestion Notification: 0 241 | 0x05, 242 | 0x94, // Total Length: 1428 243 | 0x83, 244 | 0xf6, // Identification: 0x83f6 245 | 0b0100_0000 // Flags: Don't Fragment 246 | | 0b0_0000, 247 | 0x00, // Fragment Offset: 0 248 | 0x40, // Time to Live: 64 249 | 0x01, // Protocol: 1 250 | 0xeb, 251 | 0x6e, // Header Checksum: 0xeb6e 252 | 0x02, 253 | 0x01, 254 | 0x01, 255 | 0x01, // Source Address: 2.1.1.1 256 | 0x02, 257 | 0x01, 258 | 0x01, 259 | 0x02, // Destination Address: 2.1.1.2 260 | ], 261 | bin_proto::BigEndian, 262 | )) 263 | .unwrap(); 264 | }); 265 | } 266 | } 267 | 268 | mod bench_deku { 269 | use super::*; 270 | use deku::prelude::*; 271 | 272 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 273 | #[deku(id_type = "u8")] 274 | #[deku(bits = 4)] 275 | enum Version { 276 | #[deku(id = "4")] 277 | V4, 278 | } 279 | 280 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 281 | struct Flags { 282 | #[deku(bits = 1)] 283 | reserved: bool, 284 | #[deku(bits = 1)] 285 | dont_fragment: bool, 286 | #[deku(bits = 1)] 287 | more_fragments: bool, 288 | } 289 | 290 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)] 291 | struct IPv4 { 292 | version: Version, 293 | #[deku(bits = 4)] 294 | internet_header_length: u8, 295 | #[deku(bits = 6)] 296 | differentiated_services_code_point: u8, 297 | #[deku(bits = 2)] 298 | explicit_congestion_notification: u8, 299 | total_length: u16, 300 | identification: u16, 301 | flags: Flags, 302 | #[deku(bits = 13)] 303 | fragment_offset: u16, 304 | time_to_live: u8, 305 | protocol: u8, 306 | header_checksum: u16, 307 | source_address: Ipv4Addr, 308 | destination_address: Ipv4Addr, 309 | } 310 | 311 | #[bench] 312 | fn bench_ipv4_write_deku(b: &mut Bencher) { 313 | b.iter(|| { 314 | black_box( 315 | IPv4 { 316 | version: Version::V4, 317 | internet_header_length: 5, 318 | differentiated_services_code_point: 0, 319 | explicit_congestion_notification: 0, 320 | total_length: 1428, 321 | identification: 0x83f6, 322 | flags: Flags { 323 | reserved: false, 324 | dont_fragment: true, 325 | more_fragments: false, 326 | }, 327 | fragment_offset: 0x0, 328 | time_to_live: 64, 329 | protocol: 1, 330 | header_checksum: 0xeeee, 331 | source_address: Ipv4Addr::new(2, 1, 1, 1), 332 | destination_address: Ipv4Addr::new(2, 1, 1, 2), 333 | } 334 | .to_bytes(), 335 | ) 336 | .unwrap(); 337 | }); 338 | } 339 | 340 | #[bench] 341 | fn bench_ipv4_read_deku(b: &mut Bencher) { 342 | b.iter(|| { 343 | black_box(IPv4::from_bytes(( 344 | &[ 345 | 0b0100_0000 // Version: 4 346 | | 0b0101, // Header Length: 5, 347 | 0x00, // Differentiated Services Codepoint: 0, Explicit Congestion Notification: 0 348 | 0x05, 349 | 0x94, // Total Length: 1428 350 | 0x83, 351 | 0xf6, // Identification: 0x83f6 352 | 0b0100_0000 // Flags: Don't Fragment 353 | | 0b0_0000, 354 | 0x00, // Fragment Offset: 0 355 | 0x40, // Time to Live: 64 356 | 0x01, // Protocol: 1 357 | 0xeb, 358 | 0x6e, // Header Checksum: 0xeb6e 359 | 0x02, 360 | 0x01, 361 | 0x01, 362 | 0x01, // Source Address: 2.1.1.1 363 | 0x02, 364 | 0x01, 365 | 0x01, 366 | 0x02, // Destination Address: 2.1.1.2 367 | ], 368 | 0, 369 | ))) 370 | .unwrap(); 371 | }); 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /bin-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Conversion to/from binary for arbitrary types. With `no_std` and `no_alloc` support. 2 | //! 3 | //! For more information about `#[derive(BitDecode, BitEncode)]` and its attributes, see 4 | //! [`macro@BitDecode`] or [`macro@BitEncode`]. 5 | //! 6 | //! # Example 7 | //! 8 | //! ``` 9 | //! # #[cfg(all(feature = "derive", feature = "alloc"))] 10 | //! # { 11 | //! # use bin_proto::{BitDecode, BitEncode, BitCodec}; 12 | //! #[derive(Debug, BitDecode, BitEncode, PartialEq)] 13 | //! #[bin_proto(discriminant_type = u8)] 14 | //! #[bin_proto(bits = 4)] 15 | //! enum E { 16 | //! V1 = 1, 17 | //! #[bin_proto(discriminant = 4)] 18 | //! V4, 19 | //! } 20 | //! 21 | //! #[derive(Debug, BitDecode, BitEncode, PartialEq)] 22 | //! struct S { 23 | //! #[bin_proto(bits = 1)] 24 | //! bitflag: bool, 25 | //! #[bin_proto(bits = 3)] 26 | //! bitfield: u8, 27 | //! enum_: E, 28 | //! #[bin_proto(write_value = self.arr.len() as u8)] 29 | //! arr_len: u8, 30 | //! #[bin_proto(tag = arr_len as usize)] 31 | //! arr: Vec, 32 | //! #[bin_proto(tag_type = u16, tag_value = self.prefixed_arr.len() as u16)] 33 | //! prefixed_arr: Vec, 34 | //! #[bin_proto(untagged)] 35 | //! read_to_end: Vec, 36 | //! } 37 | //! 38 | //! assert_eq!( 39 | //! S::decode_bytes(&[ 40 | //! 0b1000_0000 // bitflag: true (1) 41 | //! | 0b101_0000 // bitfield: 5 (101) 42 | //! | 0b0001, // enum_: V1 (0001) 43 | //! 0x02, // arr_len: 2 44 | //! 0x21, 0x37, // arr: [0x21, 0x37] 45 | //! 0x00, 0x01, 0x33, // prefixed_arr: [0x33] 46 | //! 0x01, 0x02, 0x03, // read_to_end: [0x01, 0x02, 0x03] 47 | //! ], bin_proto::BigEndian).unwrap().0, 48 | //! S { 49 | //! bitflag: true, 50 | //! bitfield: 5, 51 | //! enum_: E::V1, 52 | //! arr_len: 2, 53 | //! arr: vec![0x21, 0x37], 54 | //! prefixed_arr: vec![0x33], 55 | //! read_to_end: vec![0x01, 0x02, 0x03], 56 | //! } 57 | //! ); 58 | //! # } 59 | //! ``` 60 | //! 61 | //! # Manual Implementations 62 | //! 63 | //! The [`macro@BitDecode`] and [`macro@BitEncode`] derive macros support the most common use-cases, 64 | //! but it may sometimes be necessary to manually implement [`BitEncode`] or [`BitDecode`]. Both 65 | //! traits have two generic parameters: 66 | //! - `Ctx`: A mutable variable passed recursively down the codec chain 67 | //! - `Tag`: A tag for specifying additional behavior 68 | //! 69 | //! `Tag` can have any type. The following are used throughout `bin-proto` and ensure 70 | //! interoperability: 71 | //! - [`Tag`]: Specifies that an additional tag is required during decoding, such as a length prefix 72 | //! for a [`Vec`](::alloc::vec::Vec), or a discriminant of an `enum` 73 | //! - [`Untagged`]: Specifies that the type has a tag used during decoding, but this tag is not 74 | //! written during encoding 75 | //! - [`Bits`]: Specified that the type is a bitfield, and can have a variable number of bits 76 | 77 | #![cfg_attr(docsrs, feature(doc_cfg))] 78 | #![cfg_attr(docsrs, feature(rustdoc_internals))] 79 | #![cfg_attr(docsrs, allow(internal_features))] 80 | #![no_std] 81 | #![deny( 82 | missing_docs, 83 | clippy::pedantic, 84 | clippy::nursery, 85 | clippy::cargo, 86 | clippy::unwrap_used, 87 | clippy::expect_used, 88 | clippy::suspicious, 89 | clippy::complexity, 90 | clippy::perf, 91 | clippy::style 92 | )] 93 | #![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)] 94 | 95 | #[cfg(feature = "alloc")] 96 | extern crate alloc; 97 | #[cfg(feature = "std")] 98 | extern crate std; 99 | 100 | pub use self::codec::BitCodec; 101 | pub use self::codec::{BitDecode, BitDecodeExt, BitEncode, BitEncodeExt}; 102 | pub use self::discriminable::Discriminable; 103 | pub use self::error::{Error, Result}; 104 | pub use bitstream_io::{BigEndian, BitRead, BitWrite, Endianness, LittleEndian}; 105 | 106 | /// Derive the [`BitDecode`] and [`BitEncode`] traits. 107 | /// 108 | /// # Scopes 109 | /// 110 | /// ```ignore 111 | /// #[container, enum] 112 | /// enum Enum { 113 | /// #[variant] 114 | /// Variant { 115 | /// #[field] 116 | /// field: Type, 117 | /// } 118 | /// } 119 | /// 120 | /// #[container, struct] 121 | /// struct Struct { 122 | /// #[field] 123 | /// field: Type, 124 | /// } 125 | /// ``` 126 | /// 127 | /// # Using Context 128 | /// 129 | /// The [`ctx`](#ctx) and [`ctx_bounds`](#ctx_bounds) attributes can be used to specify additional 130 | /// context used during codec. The context can be accessed as `__ctx`, and the tag as `__tag` in any 131 | /// attribute macro's ``, for example in [`tag`](#tag). 132 | /// 133 | /// ``` 134 | /// # #[cfg(feature = "alloc")] 135 | /// # { 136 | /// # use bin_proto::{BitDecode, BitEncode}; 137 | /// struct Ctx { 138 | /// n: u32, 139 | /// } 140 | /// 141 | /// #[derive(BitDecode, BitEncode)] 142 | /// #[bin_proto(ctx = Ctx)] 143 | /// struct WithElementsLength { 144 | /// count: u32, 145 | /// foo: bool, 146 | /// #[bin_proto(tag = count * __ctx.n)] 147 | /// data: Vec, 148 | /// } 149 | /// # } 150 | /// ``` 151 | /// 152 | /// # Attributes 153 | /// 154 | /// | Attribute | Scope | Applicability | 155 | /// |-|-|-| 156 | /// | [`discriminant_type`](#discriminant_type) | enum | rw | 157 | /// | [`discriminant`](#discriminant) | variant | rw | 158 | /// | [`other`](#other) | variant | r | 159 | /// | [`bits`](#bits) | field, enum | rw | 160 | /// | [`untagged`](#untagged) | field | rw | 161 | /// | [`tag`](#tag) | field | rw | 162 | /// | [`tag_type`](#tag_type) | field | rw | 163 | /// | [`write_value`](#write_value) | field | w | 164 | /// | [`ctx`](#ctx) | container | rw | 165 | /// | [`ctx_bounds`](#ctx_bounds) | container | rw | 166 | /// | [`skip_encode`](#skip_encode) | field, variant | w | 167 | /// | [`skip_decode`](#skip_decode) | field, variant | r | 168 | /// | [`skip`](#skip) | field, variant | rw | 169 | /// | [`pad_before`](#pad_before) | field, struct | rw | 170 | /// | [`pad_after`](#pad_after) | field, struct | rw | 171 | /// | [`magic`](#magic) | field, struct | rw | 172 | /// 173 | /// ## `discriminant_type` 174 | /// `#[bin_proto(discriminant_type = )]` 175 | /// - ``: an arbitrary type that implements [`BitDecode`] or [`BitEncode`] 176 | /// 177 | /// Specify if enum variant should be determined by a string or interger representation of its 178 | /// discriminant. 179 | /// 180 | /// ``` 181 | /// # use bin_proto::{BitDecode, BitEncode}; 182 | /// #[derive(BitDecode, BitEncode)] 183 | /// #[bin_proto(discriminant_type = u8)] 184 | /// enum Example { 185 | /// Variant1 = 1, 186 | /// Variant5 = 5, 187 | /// } 188 | /// ``` 189 | /// 190 | /// ## `discriminant` 191 | /// `#[bin_proto(discriminant = )]` 192 | /// - ``: unique value of the discriminant's type 193 | /// 194 | /// Specify the discriminant for a variant. 195 | /// 196 | /// ``` 197 | /// # use bin_proto::{BitDecode, BitEncode}; 198 | /// #[derive(BitDecode, BitEncode)] 199 | /// #[bin_proto(discriminant_type = u8)] 200 | /// enum Example { 201 | /// #[bin_proto(discriminant = 1)] 202 | /// Variant1, 203 | /// Variant5 = 5, 204 | /// } 205 | /// ``` 206 | /// 207 | /// ## `other` 208 | /// `#[bin_proto(other)]` 209 | /// 210 | /// Decode the specified variant if the discriminant doesn't match any other variants. A 211 | /// discriminant value can still be provided for the variant, and will be used when encoding. 212 | /// 213 | /// ``` 214 | /// # use bin_proto::{BitDecode, BitEncode}; 215 | /// #[derive(BitDecode, BitEncode)] 216 | /// #[bin_proto(discriminant_type = u8)] 217 | /// enum Example { 218 | /// #[bin_proto(discriminant = 1)] 219 | /// Variant1, 220 | /// #[bin_proto(discriminant = 2, other)] 221 | /// CatchAll, 222 | /// } 223 | /// ``` 224 | /// 225 | /// ## `bits` 226 | /// `#[bin_proto(bits = )]` 227 | /// 228 | /// Determine width of field in bits. 229 | /// 230 | /// **WARNING**: Bitfields disregard endianness and instead have the same endianness as the 231 | /// underlying [`BitRead`] / [`BitWrite`] instance. If you're using bitfields, you almost always 232 | /// want a big endian stream. 233 | /// 234 | /// ``` 235 | /// # use bin_proto::{BitDecode, BitEncode}; 236 | /// #[derive(BitDecode, BitEncode)] 237 | /// struct Nibble(#[bin_proto(bits = 4)] u8); 238 | /// ``` 239 | /// 240 | /// ## `untagged` 241 | /// `#[bin_proto(untagged)]` 242 | /// 243 | /// Variable-length field is final field in container, hence lacks a length prefix and should be 244 | /// read until eof. 245 | /// 246 | /// ``` 247 | /// # #[cfg(feature = "alloc")] 248 | /// # { 249 | /// # use bin_proto::{BitDecode, BitEncode}; 250 | /// #[derive(BitDecode, BitEncode)] 251 | /// struct ReadToEnd(#[bin_proto(untagged)] Vec); 252 | /// # } 253 | /// ``` 254 | /// 255 | /// ## `tag` 256 | /// `#[bin_proto(tag = )]` 257 | /// - ``: arbitrary expression. Fields in parent container can be used without prefixing them 258 | /// with `self`. 259 | /// 260 | /// Specify tag of field. The tag represents a length prefix for variable-length fields, and a 261 | /// boolean for [`Option`]. 262 | /// 263 | /// ``` 264 | /// # #[cfg(feature = "alloc")] 265 | /// # { 266 | /// # use bin_proto::{BitDecode, BitEncode}; 267 | /// #[derive(BitDecode, BitEncode)] 268 | /// struct WithElementsLength { 269 | /// count: u32, 270 | /// foo: bool, 271 | /// #[bin_proto(tag = count as usize)] 272 | /// data: Vec, 273 | /// } 274 | /// # } 275 | /// ``` 276 | /// 277 | /// ## `tag_type` 278 | /// `#[bin_proto(tag_type = [, tag_value = ]?[, tag_bits = ]?)]` 279 | /// - ``: tag's type 280 | /// - ``: arbitrary expression. Fields in parent container should be prefixed with `self`. 281 | /// 282 | /// Specify tag of field. The tag represents a length prefix for variable-length fields, and a 283 | /// boolean for [`Option`]. The tag is placed directly before the field. The `tag_value` only has 284 | /// to be specified when deriving [`BitEncode`]. 285 | /// 286 | /// ``` 287 | /// # #[cfg(feature = "alloc")] 288 | /// # { 289 | /// # use bin_proto::{BitDecode, BitEncode}; 290 | /// #[derive(BitDecode, BitEncode)] 291 | /// struct WithElementsLength { 292 | /// #[bin_proto(tag_type = u16, tag_value = self.data.len() as u16, tag_bits = 13)] 293 | /// data: Vec, 294 | /// } 295 | /// # } 296 | /// ``` 297 | /// 298 | /// ## `write_value` 299 | /// `#[bin_proto(write_value = )]` 300 | /// - ``: An expression that can be coerced to the field type. Fields in parent container 301 | /// should be prefixed with `self`. 302 | /// 303 | /// Specify an expression that should be used as the field's value for writing. 304 | /// 305 | /// ``` 306 | /// # #[cfg(feature = "alloc")] 307 | /// # { 308 | /// # use bin_proto::{BitDecode, BitEncode}; 309 | /// #[derive(BitDecode, BitEncode)] 310 | /// struct WithElementsLengthAuto { 311 | /// #[bin_proto(write_value = self.data.len() as u32)] 312 | /// count: u32, 313 | /// foo: bool, 314 | /// #[bin_proto(tag = count as usize)] 315 | /// data: Vec, 316 | /// } 317 | /// # } 318 | /// ``` 319 | /// 320 | /// ## `ctx` 321 | /// `#[bin_proto(ctx = )[, ctx_generics([, ]*)]?]` 322 | /// - ``: The type of the context. Either a concrete type, or one of the container's generics 323 | /// - ``: Any generics used by the context type, with optional bounds. E.g. 324 | /// `T: Copy` for a [`Vec`](alloc::vec::Vec) context. 325 | /// 326 | /// Specify the type of context that will be passed to codec functions. 327 | /// 328 | /// ``` 329 | /// # #[cfg(feature = "alloc")] 330 | /// # { 331 | /// # use bin_proto::{BitDecode, BitEncode, BitEncodeExt}; 332 | /// struct Ctx; 333 | /// 334 | /// struct NeedsCtx; 335 | /// 336 | /// impl BitDecode for NeedsCtx { 337 | /// fn decode( 338 | /// _read: &mut R, 339 | /// _ctx: &mut Ctx, 340 | /// _tag: (), 341 | /// ) -> bin_proto::Result 342 | /// where 343 | /// R: bin_proto::BitRead, 344 | /// E: bin_proto::Endianness, 345 | /// { 346 | /// // Use ctx here 347 | /// Ok(Self) 348 | /// } 349 | /// } 350 | /// 351 | /// impl BitEncode for NeedsCtx { 352 | /// fn encode( 353 | /// &self, 354 | /// _write: &mut W, 355 | /// _ctx: &mut Ctx, 356 | /// _tag: (), 357 | /// ) -> bin_proto::Result<()> 358 | /// where 359 | /// W: bin_proto::BitWrite, 360 | /// E: bin_proto::Endianness, 361 | /// { 362 | /// // Use ctx here 363 | /// Ok(()) 364 | /// } 365 | /// } 366 | /// 367 | /// #[derive(BitDecode, BitEncode)] 368 | /// #[bin_proto(ctx = Ctx)] 369 | /// struct WithCtx(NeedsCtx); 370 | /// 371 | /// WithCtx(NeedsCtx) 372 | /// .encode_bytes_ctx(bin_proto::BigEndian, &mut Ctx, ()) 373 | /// .unwrap(); 374 | /// # } 375 | /// ``` 376 | /// 377 | /// ``` 378 | /// # use bin_proto::{BitDecode, BitEncode}; 379 | /// # use std::marker::PhantomData; 380 | /// #[derive(BitDecode, BitEncode)] 381 | /// #[bin_proto(ctx = Ctx)] 382 | /// struct NestedCodec + BitEncode>(A, PhantomData); 383 | /// ``` 384 | /// 385 | /// ``` 386 | /// # use bin_proto::{BitDecode, BitEncode}; 387 | /// struct Ctx<'a, T: Copy>(&'a T); 388 | /// 389 | /// #[derive(BitDecode, BitEncode)] 390 | /// #[bin_proto(ctx = Ctx<'a, T>, ctx_generics('a, T: Copy))] 391 | /// struct WithCtx; 392 | /// ``` 393 | /// 394 | /// ## `ctx_bounds` 395 | /// `#[bin_proto(ctx_bounds([, ]*)[, ctx_generics([, ]*)]?)]` 396 | /// - ``: Trait bounds that must be satisfied by the context 397 | /// - ``: Any generics used by the context type. E.g. `'a` for a context with a 398 | /// [`From<&'a i32>`](From) bound. 399 | /// 400 | /// Specify the trait bounds of context that will be passed to codec functions. 401 | /// 402 | /// ``` 403 | /// # use bin_proto::{BitDecode, BitEncode}; 404 | /// trait CtxTrait {}; 405 | /// 406 | /// struct NeedsCtx; 407 | /// 408 | /// impl BitDecode for NeedsCtx { 409 | /// fn decode( 410 | /// _read: &mut R, 411 | /// _ctx: &mut Ctx, 412 | /// _tag: (), 413 | /// ) -> bin_proto::Result 414 | /// where 415 | /// R: bin_proto::BitRead, 416 | /// E: bin_proto::Endianness, 417 | /// { 418 | /// // Use ctx here 419 | /// Ok(Self) 420 | /// } 421 | ///} 422 | /// 423 | /// impl BitEncode for NeedsCtx { 424 | /// fn encode( 425 | /// &self, 426 | /// _write: &mut W, 427 | /// _ctx: &mut Ctx, 428 | /// _tag: (), 429 | /// ) -> bin_proto::Result<()> 430 | /// where 431 | /// W: bin_proto::BitWrite, 432 | /// E: bin_proto::Endianness, 433 | /// { 434 | /// // Use ctx here 435 | /// Ok(()) 436 | /// } 437 | /// } 438 | /// 439 | /// #[derive(BitDecode, BitEncode)] 440 | /// #[bin_proto(ctx_bounds(CtxTrait))] 441 | /// struct WithCtx(NeedsCtx); 442 | /// ``` 443 | /// 444 | /// ``` 445 | /// # use bin_proto::{BitDecode, BitEncode}; 446 | /// #[derive(BitDecode, BitEncode)] 447 | /// #[bin_proto(ctx_bounds(From<&'a i32>), ctx_generics('a))] 448 | /// struct WithCtx; 449 | /// ``` 450 | /// 451 | /// ## `skip_encode` 452 | /// `#[bin_proto(skip_encode)]` 453 | /// 454 | /// If applied to a field, skip the field when encoding. If applied to an enum variant, return an 455 | /// Error if the variant is attempted to be encoded. 456 | /// 457 | /// ``` 458 | /// # use bin_proto::{BitDecode, BitEncode}; 459 | /// #[derive(BitDecode, BitEncode)] 460 | /// struct Struct(#[bin_proto(skip_encode)] u8); 461 | /// ``` 462 | /// 463 | /// ``` 464 | /// # use bin_proto::BitEncode; 465 | /// #[derive(BitEncode)] 466 | /// #[bin_proto(discriminant_type = u8)] 467 | /// enum Enum { 468 | /// #[bin_proto(skip_encode)] 469 | /// Skip 470 | /// } 471 | /// ``` 472 | /// 473 | /// ## `skip_decode` 474 | /// `#[bin_proto(skip_decode)]` 475 | /// 476 | /// If applied to a field, use [`Default::default`] instead of attempting to read field. If applied 477 | /// to an enum variant, don't generate code for decoding. 478 | /// 479 | /// ``` 480 | /// # use bin_proto::{BitDecode, BitEncode}; 481 | /// #[derive(BitDecode, BitEncode)] 482 | /// struct Struct(#[bin_proto(skip_decode)] u8); 483 | /// ``` 484 | /// 485 | /// ``` 486 | /// # use bin_proto::BitDecode; 487 | /// #[derive(BitDecode)] 488 | /// #[bin_proto(discriminant_type = u8)] 489 | /// enum Enum { 490 | /// #[bin_proto(skip_decode)] 491 | /// Skip 492 | /// } 493 | /// ``` 494 | /// 495 | /// ## `skip` 496 | /// `#[bin_proto(skip)]` 497 | /// 498 | /// Equivalent to combining [`skip_encode`](#skip_encode) and [`skip_decode`](#skip_decode). 499 | /// 500 | /// ``` 501 | /// # use bin_proto::{BitDecode, BitEncode}; 502 | /// #[derive(BitDecode, BitEncode)] 503 | /// struct Struct(#[bin_proto(skip)] u8); 504 | /// ``` 505 | /// 506 | /// ``` 507 | /// # use bin_proto::{BitDecode, BitEncode}; 508 | /// #[derive(BitDecode, BitEncode)] 509 | /// #[bin_proto(discriminant_type = u8)] 510 | /// enum Enum { 511 | /// #[bin_proto(skip)] 512 | /// Skip 513 | /// } 514 | /// ``` 515 | /// 516 | /// ## `pad_before` 517 | /// `#[bin_proto(pad_before = )]` 518 | /// 519 | /// Insert 0 bits when writing and skip bits when reading, prior to processing the field. 520 | /// 521 | /// ``` 522 | /// # use bin_proto::{BitDecode, BitEncode}; 523 | /// #[derive(BitDecode, BitEncode)] 524 | /// struct Struct(#[bin_proto(pad_before = 3)] u8); 525 | /// ``` 526 | /// 527 | /// ## `pad_after` 528 | /// `#[bin_proto(pad_after = )]` 529 | /// 530 | /// Insert 0 bits when writing and skip bits when reading, after processing the field. 531 | /// 532 | /// ``` 533 | /// # use bin_proto::{BitDecode, BitEncode}; 534 | /// #[derive(BitDecode, BitEncode)] 535 | /// struct Struct(#[bin_proto(pad_after = 3)] u8); 536 | /// ``` 537 | /// 538 | /// ## `magic` 539 | /// `#[bin_proto(magic = )]` 540 | /// - ``: Must evaluate to `&[u8; _]` 541 | /// 542 | /// Indicates that the value must be present immediately preceding the field or struct. 543 | /// 544 | /// ``` 545 | /// # use bin_proto::{BitDecode, BitEncode}; 546 | /// #[derive(BitDecode, BitEncode)] 547 | /// #[bin_proto(magic = &[0x01, 0x02, 0x03])] 548 | /// struct Magic(#[bin_proto(magic = b"123")] u8); 549 | /// ``` 550 | #[cfg(feature = "derive")] 551 | pub use bin_proto_derive::{BitDecode, BitEncode}; 552 | 553 | #[macro_use] 554 | mod codec; 555 | 556 | mod discriminable; 557 | mod error; 558 | mod impls; 559 | mod util; 560 | 561 | pub extern crate bitstream_io; 562 | 563 | /// A marker for [`BitEncode`] implementors that don't prepend their tag, and [`BitDecode`] 564 | /// implementors that usually have a tag, but can be read to EOF 565 | pub struct Untagged; 566 | 567 | /// A marker for [`BitDecode`] implementors that require a tag. 568 | pub struct Tag(pub T); 569 | 570 | /// A marker for [`BitDecode`] and [`BitEncode`] implementors that support bitfield operations. 571 | pub struct Bits; 572 | 573 | /// ```compile_fail 574 | /// # use bin_proto::{BitDecode, BitEncode}; 575 | /// #[derive(BitDecode, BitEncode)] 576 | /// struct MutuallyExclusiveAttrs { 577 | /// pub length: u8, 578 | /// #[bin_proto(untagged)] 579 | /// #[bin_proto(tag = length as usize)] 580 | /// pub reason: alloc::string::String, 581 | /// } 582 | /// ``` 583 | #[cfg(all(feature = "derive", feature = "alloc", doctest))] 584 | #[allow(unused)] 585 | fn compile_fail_if_multiple_exclusive_attrs() {} 586 | --------------------------------------------------------------------------------