├── examples ├── wasmjs │ ├── .gitignore │ ├── build.rs │ ├── src │ │ ├── payloads │ │ │ ├── mod.rs │ │ │ ├── d.rs │ │ │ ├── a.rs │ │ │ ├── b.rs │ │ │ └── c.rs │ │ ├── blocks │ │ │ ├── block_f32.rs │ │ │ ├── block_f64.rs │ │ │ ├── block_i16.rs │ │ │ ├── block_i32.rs │ │ │ ├── block_i64.rs │ │ │ ├── block_i8.rs │ │ │ ├── block_u16.rs │ │ │ ├── block_u32.rs │ │ │ ├── block_u64.rs │ │ │ ├── block_u8.rs │ │ │ ├── block_bool.rs │ │ │ ├── block_i128.rs │ │ │ ├── block_u128.rs │ │ │ ├── block_comb.rs │ │ │ ├── mod.rs │ │ │ └── block_enums.rs │ │ └── err.rs │ ├── build.sh │ └── Cargo.toml ├── build.sh └── Cargo.toml ├── brec ├── TODOs.md ├── src │ ├── payload │ │ ├── defaults │ │ │ ├── mod.rs │ │ │ ├── vec_u8.rs │ │ │ └── string.rs │ │ └── mod.rs │ ├── traits │ │ ├── mod.rs │ │ ├── read │ │ │ └── status.rs │ │ └── props │ │ │ ├── mod.rs │ │ │ └── byte_block.rs │ ├── prelude.rs │ ├── build │ │ └── mod.rs │ ├── lib.rs │ ├── storage │ │ ├── slot │ │ │ ├── write.rs │ │ │ └── header.rs │ │ └── locator.rs │ ├── packet │ │ ├── referred.rs │ │ └── header │ │ │ └── write.rs │ └── error.rs ├── LICENSE.txt └── Cargo.toml ├── brec_macros ├── src │ ├── modificators │ │ ├── mod.rs │ │ └── attrs.rs │ ├── parser │ │ ├── mod.rs │ │ ├── block.rs │ │ └── payload.rs │ ├── tokenized │ │ ├── mod.rs │ │ └── block │ │ │ ├── to_bytes │ │ │ ├── mod.rs │ │ │ └── field │ │ │ │ └── mod.rs │ │ │ ├── type_def │ │ │ ├── mod.rs │ │ │ └── ty │ │ │ │ └── mod.rs │ │ │ ├── assignation │ │ │ ├── mod.rs │ │ │ └── field │ │ │ │ └── mod.rs │ │ │ ├── from_bytes │ │ │ ├── mod.rs │ │ │ ├── field │ │ │ │ └── mod.rs │ │ │ └── ty │ │ │ │ └── mod.rs │ │ │ ├── read_exact │ │ │ ├── mod.rs │ │ │ └── field │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ ├── tests │ │ ├── tcrate │ │ │ ├── build.rs.test │ │ │ └── cargo.toml.test │ │ ├── field.rs │ │ ├── struct.rs │ │ ├── packet.rs │ │ └── project │ │ │ └── mod.rs │ ├── parsing │ │ ├── mod.rs │ │ ├── field │ │ │ └── mod.rs │ │ ├── payload │ │ │ ├── mod.rs │ │ │ └── attr │ │ │ │ └── mod.rs │ │ ├── block │ │ │ ├── mod.rs │ │ │ └── attr │ │ │ │ └── mod.rs │ │ └── ty │ │ │ └── mod.rs │ ├── codegen │ │ ├── mod.rs │ │ ├── props │ │ │ ├── mod.rs │ │ │ ├── payload.rs │ │ │ └── block.rs │ │ ├── base │ │ │ ├── mod.rs │ │ │ ├── payload.rs │ │ │ └── block.rs │ │ ├── write │ │ │ ├── mod.rs │ │ │ └── payload.rs │ │ └── read │ │ │ ├── mod.rs │ │ │ └── payload.rs │ ├── entities │ │ ├── mod.rs │ │ ├── field │ │ │ └── mod.rs │ │ ├── payload │ │ │ ├── mod.rs │ │ │ └── attr │ │ │ │ └── mod.rs │ │ ├── vis │ │ │ └── mod.rs │ │ ├── derives │ │ │ └── mod.rs │ │ ├── block │ │ │ ├── attr │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── modpath │ │ │ └── mod.rs │ │ └── ty │ │ │ └── mod.rs │ ├── generate │ │ ├── mod.rs │ │ ├── config.rs │ │ └── parser.rs │ ├── error.rs │ └── collector │ │ ├── payloads │ │ ├── enums.rs │ │ ├── mod.rs │ │ ├── write.rs │ │ └── props.rs │ │ ├── packet.rs │ │ ├── blocks │ │ ├── enums.rs │ │ ├── mod.rs │ │ ├── write.rs │ │ └── props.rs │ │ └── mod.rs ├── README.md ├── LICENSE.txt ├── stress.sh ├── Cargo.toml └── Cargo.lock ├── .gitignore ├── tests ├── build.rs ├── measurements │ ├── build.rs │ ├── src │ │ ├── tests │ │ │ ├── mod.rs │ │ │ ├── text.rs │ │ │ └── json.rs │ │ ├── main.rs │ │ ├── content.rs │ │ └── test.rs │ └── Cargo.toml ├── locked_storage │ ├── build.rs │ ├── src │ │ ├── main.rs │ │ ├── test.rs │ │ └── storage.rs │ └── Cargo.toml ├── stress_blocks │ ├── build.rs │ ├── stress.sh │ ├── src │ │ ├── block_i8.rs │ │ ├── block_i16.rs │ │ ├── block_i32.rs │ │ ├── block_i64.rs │ │ ├── block_u16.rs │ │ ├── block_u32.rs │ │ ├── block_u64.rs │ │ ├── block_bool.rs │ │ ├── block_i128.rs │ │ ├── block_u128.rs │ │ ├── block_u8.rs │ │ ├── block_f32.rs │ │ ├── block_f64.rs │ │ ├── block_blob.rs │ │ ├── block_blobs_max.rs │ │ ├── main.rs │ │ └── block_enums.rs │ └── Cargo.toml ├── stress_packets │ ├── build.rs │ ├── stress.sh │ ├── src │ │ ├── main.rs │ │ ├── blocks │ │ │ ├── block_i8.rs │ │ │ ├── block_i16.rs │ │ │ ├── block_i32.rs │ │ │ ├── block_i64.rs │ │ │ ├── block_u16.rs │ │ │ ├── block_u32.rs │ │ │ ├── block_u64.rs │ │ │ ├── block_u8.rs │ │ │ ├── block_bool.rs │ │ │ ├── block_i128.rs │ │ │ ├── block_u128.rs │ │ │ ├── block_f32.rs │ │ │ ├── block_f64.rs │ │ │ ├── block_blob.rs │ │ │ ├── block_blobs_max.rs │ │ │ ├── block_enums.rs │ │ │ └── mod.rs │ │ └── payloads │ │ │ ├── mod.rs │ │ │ └── d.rs │ └── Cargo.toml ├── stress_payloads │ ├── build.rs │ ├── stress.sh │ ├── src │ │ ├── main.rs │ │ └── d.rs │ └── Cargo.toml ├── stress.sh ├── test.sh └── Cargo.toml ├── stress.sh ├── test.sh ├── lint.sh ├── LICENSE.txt ├── Cargo.toml ├── site ├── mkdocs.yml └── docs │ ├── stability │ └── tests.md │ ├── overview.md │ └── parts │ └── packets.md ├── .github └── workflows │ ├── mkdocs_pages.yml │ └── on_pull_request.yml ├── CHANGELOG.md └── CONTRIBUTING.md /examples/wasmjs/.gitignore: -------------------------------------------------------------------------------- 1 | ./pkg -------------------------------------------------------------------------------- /brec/TODOs.md: -------------------------------------------------------------------------------- 1 | - add example based on wasm -------------------------------------------------------------------------------- /brec_macros/src/modificators/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attrs; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/target 3 | gen_tests 4 | /not_public 5 | -------------------------------------------------------------------------------- /brec/src/payload/defaults/mod.rs: -------------------------------------------------------------------------------- 1 | mod string; 2 | mod vec_u8; 3 | -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ./wasmjs 4 | sh ./build.sh -------------------------------------------------------------------------------- /tests/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /brec_macros/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod block; 2 | pub mod payload; 3 | -------------------------------------------------------------------------------- /examples/wasmjs/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/measurements/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | 3 | pub(crate) use block::*; 4 | -------------------------------------------------------------------------------- /tests/locked_storage/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/stress_blocks/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/stress_packets/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/stress_payloads/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } 4 | -------------------------------------------------------------------------------- /brec_macros/src/tests/tcrate/build.rs.test: -------------------------------------------------------------------------------- 1 | fn main() { 2 | brec::build_setup(); 3 | } -------------------------------------------------------------------------------- /brec_macros/src/parsing/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod field; 3 | mod payload; 4 | mod ty; 5 | -------------------------------------------------------------------------------- /brec/src/traits/mod.rs: -------------------------------------------------------------------------------- 1 | mod props; 2 | mod read; 3 | mod write; 4 | 5 | pub use props::*; 6 | pub use read::*; 7 | pub use write::*; 8 | -------------------------------------------------------------------------------- /tests/measurements/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod json; 2 | pub mod storage; 3 | pub mod stream; 4 | pub mod streamed_storage; 5 | pub mod text; 6 | -------------------------------------------------------------------------------- /stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd ./tests 5 | sh stress.sh 6 | cd .. 7 | 8 | cd ./brec_macros 9 | sh stress.sh 10 | cd .. 11 | -------------------------------------------------------------------------------- /examples/wasmjs/src/payloads/mod.rs: -------------------------------------------------------------------------------- 1 | mod a; 2 | mod b; 3 | mod c; 4 | mod d; 5 | 6 | pub use a::*; 7 | pub use b::*; 8 | pub use c::*; 9 | pub use d::*; 10 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd ./brec 5 | cargo test --features locked_storage -- --nocapture 6 | cd .. 7 | 8 | cd ./tests 9 | sh test.sh 10 | -------------------------------------------------------------------------------- /tests/stress_blocks/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export BREC_STRESS_BLOCKS_CASES=500 4 | export BREC_STRESS_BLOCKS_MAX_COUNT=2000 5 | 6 | cargo test --release -- --nocapture 7 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_f32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockF32 { 6 | field: f32, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_f64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockF64 { 6 | field: f64, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_i16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockI16 { 6 | field: i16, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_i32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockI32 { 6 | field: i32, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_i64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockI64 { 6 | field: i64, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_i8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockI8 { 6 | field: i8, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_u16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockU16 { 6 | field: u16, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_u32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockU32 { 6 | field: u32, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_u64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockU64 { 6 | field: u64, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_u8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockU8 { 6 | field: u8, 7 | } 8 | -------------------------------------------------------------------------------- /tests/stress_packets/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export BREC_STRESS_PACKETS_CASES=200 4 | export BREC_STRESS_PACKETS_MAX_COUNT=2000 5 | 6 | cargo test --release -- --nocapture 7 | -------------------------------------------------------------------------------- /tests/stress_payloads/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export BREC_STRESS_PAYLOADS_CASES=500 4 | export BREC_STRESS_PAYLOADS_MAX_COUNT=2000 5 | 6 | cargo test --release -- --nocapture 7 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_bool.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockBool { 6 | field: bool, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_i128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockI128 { 6 | field: i128, 7 | } 8 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_u128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockU128 { 6 | field: u128, 7 | } 8 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "wasmjs" 5 | ] 6 | 7 | [workspace.dependencies] 8 | brec = { path = "../brec" } 9 | serde = "1.0" 10 | serde_json = "1.0.140" -------------------------------------------------------------------------------- /brec_macros/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | mod base; 2 | mod props; 3 | mod read; 4 | mod write; 5 | 6 | pub(crate) use base::*; 7 | pub(crate) use props::*; 8 | pub(crate) use read::*; 9 | pub(crate) use write::*; 10 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/to_bytes/mod.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | 3 | use crate::*; 4 | use proc_macro2::TokenStream; 5 | 6 | pub trait ToBytes { 7 | fn to_bytes(&self, blob_by_ref: bool) -> Result; 8 | } 9 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/type_def/mod.rs: -------------------------------------------------------------------------------- 1 | mod ty; 2 | 3 | use proc_macro2::TokenStream; 4 | 5 | pub trait TypeDefinition { 6 | fn direct(&self) -> TokenStream; 7 | fn referenced(&self) -> TokenStream; 8 | } 9 | -------------------------------------------------------------------------------- /tests/locked_storage/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod protocol; 3 | 4 | #[cfg(test)] 5 | pub(crate) use protocol::*; 6 | 7 | #[cfg(test)] 8 | mod storage; 9 | #[cfg(test)] 10 | mod test; 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/assignation/mod.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | 3 | use proc_macro2::TokenStream; 4 | 5 | pub trait Assignation { 6 | fn direct_ty(&self) -> TokenStream; 7 | fn referenced_ty(&self) -> TokenStream; 8 | } 9 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/from_bytes/mod.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | mod ty; 3 | 4 | use proc_macro2::TokenStream; 5 | use syn::Ident; 6 | 7 | pub trait FromBytes { 8 | fn safe(&self, src: &Ident, from: usize, to: usize) -> TokenStream; 9 | } 10 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/read_exact/mod.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | 3 | use crate::*; 4 | use proc_macro2::TokenStream; 5 | use syn::Ident; 6 | 7 | pub trait ReadExact { 8 | fn read_exact(&self, src: &Ident) -> Result; 9 | } 10 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ./brec 4 | cargo +nightly clippy --tests --all --all-features -- -D warnings 5 | cargo test --release 6 | cd .. 7 | 8 | cd ./brec_macros 9 | cargo +nightly clippy --tests --all --all-features -- -D warnings 10 | cargo test --release 11 | cd .. 12 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/props/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod payload; 3 | 4 | use crate::*; 5 | 6 | use proc_macro2::TokenStream; 7 | 8 | pub trait Size { 9 | fn gen(&self) -> TokenStream; 10 | } 11 | 12 | pub trait Crc { 13 | fn gen(&self) -> Result; 14 | } 15 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/base/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod payload; 3 | 4 | use crate::*; 5 | use proc_macro2::TokenStream; 6 | 7 | pub trait Gen { 8 | fn gen(&self) -> Result; 9 | } 10 | 11 | pub trait Base { 12 | fn gen(&self) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/write/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod payload; 3 | 4 | use crate::*; 5 | use proc_macro2::TokenStream; 6 | 7 | pub trait Write { 8 | fn gen(&self) -> Result; 9 | } 10 | 11 | pub trait WriteVectored { 12 | fn gen(&self) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /tests/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd ./stress_blocks 5 | sh ./stress.sh 6 | cd .. 7 | 8 | cd ./stress_payloads 9 | sh ./stress.sh 10 | cd .. 11 | 12 | cd ./stress_packets 13 | sh ./stress.sh 14 | cd .. 15 | 16 | cd ./measurements 17 | cargo test --release -- --nocapture 18 | cd .. 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod blocks; 3 | #[cfg(test)] 4 | mod payloads; 5 | #[cfg(test)] 6 | mod test; 7 | 8 | #[cfg(test)] 9 | pub(crate) use blocks::*; 10 | #[cfg(test)] 11 | pub(crate) use payloads::*; 12 | #[cfg(test)] 13 | pub(crate) use test::*; 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/mod.rs: -------------------------------------------------------------------------------- 1 | mod assignation; 2 | mod from_bytes; 3 | mod read_exact; 4 | mod to_bytes; 5 | mod type_def; 6 | 7 | pub(crate) use assignation::*; 8 | pub(crate) use from_bytes::*; 9 | pub(crate) use read_exact::*; 10 | pub(crate) use to_bytes::*; 11 | pub(crate) use type_def::*; 12 | -------------------------------------------------------------------------------- /brec/src/prelude.rs: -------------------------------------------------------------------------------- 1 | extern crate brec_macros; 2 | 3 | pub use crate::error::*; 4 | pub use crate::packet::*; 5 | pub use crate::payload::*; 6 | pub use crate::storage::*; 7 | pub use crate::traits::*; 8 | 9 | #[cfg(feature = "bincode")] 10 | pub use bincode; 11 | pub use brec_macros::*; 12 | pub use crc32fast; 13 | -------------------------------------------------------------------------------- /tests/measurements/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod content; 3 | #[cfg(test)] 4 | mod protocol; 5 | 6 | #[cfg(test)] 7 | pub(crate) use content::*; 8 | #[cfg(test)] 9 | pub(crate) use protocol::*; 10 | 11 | #[cfg(test)] 12 | mod report; 13 | #[cfg(test)] 14 | mod test; 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /brec_macros/src/entities/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod derives; 3 | mod field; 4 | mod modpath; 5 | mod payload; 6 | mod ty; 7 | mod vis; 8 | 9 | pub(crate) use block::*; 10 | pub(crate) use derives::*; 11 | pub(crate) use field::*; 12 | pub(crate) use modpath::*; 13 | pub(crate) use payload::*; 14 | pub(crate) use ty::*; 15 | pub(crate) use vis::*; 16 | -------------------------------------------------------------------------------- /brec_macros/src/tests/tcrate/cargo.toml.test: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_case" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | brec = { path = "../../brec", features=["bincode"] } 8 | serde = { version = "1.0", features = ["derive"] } 9 | 10 | [build-dependencies] 11 | brec = { path = "../../brec", features=["build"]} 12 | 13 | [workspace] 14 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd ./stress_blocks 5 | cargo test --release -- --nocapture 6 | cd .. 7 | 8 | cd ./stress_payloads 9 | cargo test --release -- --nocapture 10 | cd .. 11 | 12 | cd ./stress_packets 13 | cargo test --release -- --nocapture 14 | cd .. 15 | 16 | cd ./locked_storage 17 | cargo test --release -- --nocapture 18 | cd .. 19 | 20 | -------------------------------------------------------------------------------- /tests/stress_payloads/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod a; 3 | #[cfg(test)] 4 | mod b; 5 | #[cfg(test)] 6 | mod c; 7 | #[cfg(test)] 8 | mod d; 9 | #[cfg(test)] 10 | mod test; 11 | 12 | #[cfg(test)] 13 | pub(crate) use a::*; 14 | #[cfg(test)] 15 | pub(crate) use b::*; 16 | #[cfg(test)] 17 | pub(crate) use c::*; 18 | #[cfg(test)] 19 | pub(crate) use d::*; 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "measurements", 5 | "stress_blocks", 6 | "stress_payloads", 7 | "stress_packets", 8 | "locked_storage" 9 | ] 10 | 11 | [workspace.dependencies] 12 | brec = { path = "../brec" } 13 | serde = "1.0" 14 | serde_json = "1.0.140" 15 | proptest = "1.4" 16 | num-format = "0.4" 17 | serial_test = "3.2.0" 18 | -------------------------------------------------------------------------------- /examples/wasmjs/src/payloads/d.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[payload(bincode)] 4 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 5 | pub enum PayloadD { 6 | U8(u8), 7 | U16(u16), 8 | U32(u32), 9 | U64(u64), 10 | U128(u128), 11 | I8(i8), 12 | I16(i16), 13 | I32(i32), 14 | I64(i64), 15 | I128(i128), 16 | String(String), 17 | } 18 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_i8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockI8 { 7 | field: i8, 8 | } 9 | 10 | impl Arbitrary for BlockI8 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI8 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/wasmjs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! command -v wasm-pack &> /dev/null; then 4 | echo "wasm-pack not found. Installing..." 5 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 6 | else 7 | echo "wasm-pack is already installed." 8 | fi 9 | 10 | # Check nodejs target 11 | wasm-pack build --target nodejs 12 | 13 | # Check bundler 14 | wasm-pack build --target bundler 15 | 16 | # Check web target 17 | wasm-pack build --target web -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_i16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockI16 { 7 | field: i16, 8 | } 9 | 10 | impl Arbitrary for BlockI16 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI16 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_i32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockI32 { 7 | field: i32, 8 | } 9 | 10 | impl Arbitrary for BlockI32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI32 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_i64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockI64 { 7 | field: i64, 8 | } 9 | 10 | impl Arbitrary for BlockI64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI64 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_u16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockU16 { 7 | field: u16, 8 | } 9 | 10 | impl Arbitrary for BlockU16 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU16 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_u32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockU32 { 7 | field: u32, 8 | } 9 | 10 | impl Arbitrary for BlockU32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU32 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_u64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockU64 { 7 | field: u64, 8 | } 9 | 10 | impl Arbitrary for BlockU64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU64 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/from_bytes/field/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | use syn::Ident; 4 | 5 | use crate::*; 6 | 7 | impl FromBytes for Field { 8 | fn safe(&self, src: &Ident, from: usize, to: usize) -> TokenStream { 9 | let name = format_ident!("{}", self.name); 10 | let ty = self.ty.safe(src, from, to); 11 | quote! { 12 | let #name = #ty; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/read/mod.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | mod payload; 3 | 4 | use crate::*; 5 | use proc_macro2::TokenStream; 6 | 7 | pub trait Read { 8 | fn gen(&self) -> Result; 9 | } 10 | 11 | pub trait ReadFromSlice { 12 | fn gen(&self) -> Result; 13 | } 14 | 15 | pub trait TryRead { 16 | fn gen(&self) -> Result; 17 | } 18 | 19 | pub trait TryReadBuffered { 20 | fn gen(&self) -> Result; 21 | } 22 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_bool.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockBool { 7 | field: bool, 8 | } 9 | 10 | impl Arbitrary for BlockBool { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockBool { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_i128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockI128 { 7 | field: i128, 8 | } 9 | 10 | impl Arbitrary for BlockI128 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI128 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_u128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockU128 { 7 | field: u128, 8 | } 9 | 10 | impl Arbitrary for BlockU128 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU128 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_u8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 6 | pub struct BlockU8 { 7 | field: u8, 8 | } 9 | 10 | impl Arbitrary for BlockU8 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU8 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_i8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockI8 { 7 | field: i8, 8 | } 9 | 10 | impl Arbitrary for BlockI8 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI8 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_i16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockI16 { 7 | field: i16, 8 | } 9 | 10 | impl Arbitrary for BlockI16 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI16 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_i32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockI32 { 7 | field: i32, 8 | } 9 | 10 | impl Arbitrary for BlockI32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI32 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_i64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockI64 { 7 | field: i64, 8 | } 9 | 10 | impl Arbitrary for BlockI64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI64 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_u16.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockU16 { 7 | field: u16, 8 | } 9 | 10 | impl Arbitrary for BlockU16 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU16 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_u32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockU32 { 7 | field: u32, 8 | } 9 | 10 | impl Arbitrary for BlockU32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU32 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_u64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockU64 { 7 | field: u64, 8 | } 9 | 10 | impl Arbitrary for BlockU64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU64 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_u8.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 6 | pub struct BlockU8 { 7 | field: u8, 8 | } 9 | 10 | impl Arbitrary for BlockU8 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU8 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_bool.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockBool { 7 | field: bool, 8 | } 9 | 10 | impl Arbitrary for BlockBool { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockBool { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_i128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockI128 { 7 | field: i128, 8 | } 9 | 10 | impl Arbitrary for BlockI128 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockI128 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_u128.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockU128 { 7 | field: u128, 8 | } 9 | 10 | impl Arbitrary for BlockU128 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::().prop_map(|field| BlockU128 { field }).boxed() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_comb.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[block] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct BlockCombination { 6 | pub field_u8: u8, 7 | pub field_u16: u16, 8 | pub field_u32: u32, 9 | pub field_u64: u64, 10 | pub field_u128: u128, 11 | pub field_i8: i8, 12 | pub field_i16: i16, 13 | pub field_i32: i32, 14 | pub field_i64: i64, 15 | pub field_i128: i128, 16 | pub field_f32: f32, 17 | pub field_f64: f64, 18 | pub field_bool: bool, 19 | } 20 | -------------------------------------------------------------------------------- /brec_macros/README.md: -------------------------------------------------------------------------------- 1 | `brec_macros` is a component of the `brec` crate. While it can technically be used independently, it does **not** provide the full functionality that `brec` offers on its own. 2 | 3 | `brec` is a toolkit for quickly and easily creating custom message exchange protocols with built-in resilience to data corruption and the ability to extract messages from mixed streams (i.e., streams that contain both `brec` packets and arbitrary non-`brec` data). 4 | 5 | For complete documentation and usage details, please visit the main `brec` repository. -------------------------------------------------------------------------------- /tests/stress_blocks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stress_blocks" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | brec = { workspace = true, features=["bincode"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | 13 | [dev-dependencies] 14 | proptest.workspace = true 15 | num-format.workspace = true 16 | 17 | [build-dependencies] 18 | brec = { workspace = true, features=["build"] } 19 | -------------------------------------------------------------------------------- /tests/stress_packets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stress_packets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | brec = { workspace = true, features=["bincode"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | 13 | [dev-dependencies] 14 | proptest.workspace = true 15 | num-format.workspace = true 16 | 17 | [build-dependencies] 18 | brec = { workspace = true, features=["build"] } 19 | -------------------------------------------------------------------------------- /tests/stress_payloads/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stress_payloads" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | brec = { workspace = true, features=["bincode"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | 13 | [dev-dependencies] 14 | proptest.workspace = true 15 | num-format.workspace = true 16 | 17 | [build-dependencies] 18 | brec = { workspace = true, features=["build"] } 19 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_f32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockF32 { 7 | field: f32, 8 | } 9 | 10 | impl Arbitrary for BlockF32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::() 17 | .prop_filter("no NaNs or infinite", |f| f.is_finite()) 18 | .prop_map(|field| BlockF32 { field }) 19 | .boxed() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_f64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockF64 { 7 | field: f64, 8 | } 9 | 10 | impl Arbitrary for BlockF64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::() 17 | .prop_filter("no NaNs or infinite", |f| f.is_finite()) 18 | .prop_map(|field| BlockF64 { field }) 19 | .boxed() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/wasmjs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmjs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | brec = { workspace = true, features=["bincode"] } 14 | serde = { workspace = true, features = ["derive"] } 15 | wasm-bindgen = "0.2" 16 | serde-wasm-bindgen = "0.6" 17 | thiserror = "2.0" 18 | 19 | [build-dependencies] 20 | brec = { workspace = true, features=["build"] } 21 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_f32.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockF32 { 7 | field: f32, 8 | } 9 | 10 | impl Arbitrary for BlockF32 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::() 17 | .prop_filter("no NaNs or infinite", |f| f.is_finite()) 18 | .prop_map(|field| BlockF32 { field }) 19 | .boxed() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_f64.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockF64 { 7 | field: f64, 8 | } 9 | 10 | impl Arbitrary for BlockF64 { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | any::() 17 | .prop_filter("no NaNs or infinite", |f| f.is_finite()) 18 | .prop_map(|field| BlockF64 { field }) 19 | .boxed() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2025 Dmitry Astafyev 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /brec/src/build/mod.rs: -------------------------------------------------------------------------------- 1 | /// Prepares the build environment to enable the `brec::generate!()` macro. 2 | /// 3 | /// This function should be called from your `build.rs` script as follows: 4 | /// 5 | /// ```rust 6 | /// brec::build_setup(); 7 | /// ``` 8 | /// 9 | /// Calling this ensures that the required code generation step is executed during 10 | /// the build process, allowing the `brec::generate!()` macro to function properly. 11 | pub fn build_setup() { 12 | std::env::var("OUT_DIR").expect("OUT_DIR not set; required for brec crate"); 13 | println!("cargo:rerun-if-changed=build.rs"); 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "brec_macros", 5 | "brec" 6 | ] 7 | exclude = [ 8 | "gen_tests/*" 9 | ] 10 | 11 | [workspace.dependencies] 12 | thiserror = "2.0" 13 | uuid = "1.8" 14 | proptest = "1.4" 15 | paste = "1.0" 16 | syn = { version = "2.0", features = ["full"] } 17 | quote = "1.0" 18 | proc-macro2 = "1.0" 19 | enum_ids = "0.7" 20 | crc32fast = "1.4" 21 | lazy_static = "1.5" 22 | serde = { version = "1.0", features = ["derive"] } 23 | bincode = "1.3" 24 | brec_macros = { version = "0.2", path = "./brec_macros" } 25 | fs4 = { version = "0.13", features = ["sync"] } -------------------------------------------------------------------------------- /brec/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2025 Dmitry Astafyev 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /tests/measurements/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "measurements" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | brec = { workspace = true, features=["bincode"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | 13 | [dev-dependencies] 14 | proptest.workspace = true 15 | num-format.workspace = true 16 | serde_json.workspace = true 17 | serial_test.workspace = true 18 | 19 | [build-dependencies] 20 | brec = { workspace = true, features=["build"] } 21 | -------------------------------------------------------------------------------- /brec_macros/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2025 Dmitry Astafyev 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/assignation/field/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | 5 | impl Assignation for Field { 6 | fn referenced_ty(&self) -> TokenStream { 7 | let name = format_ident!("{}", self.name); 8 | let ty = self.ty.referenced(); 9 | quote! { 10 | #name: #ty, 11 | } 12 | } 13 | fn direct_ty(&self) -> TokenStream { 14 | let name = format_ident!("{}", self.name); 15 | let ty = self.ty.direct(); 16 | quote! { 17 | #name: #ty, 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/locked_storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "locked_storage" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "This crate is intended for testing brec" 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | brec = { workspace = true, features=["bincode", "locked_storage"] } 11 | serde = { workspace = true, features = ["derive"] } 12 | 13 | [dev-dependencies] 14 | proptest.workspace = true 15 | num-format.workspace = true 16 | serde_json.workspace = true 17 | serial_test.workspace = true 18 | 19 | [build-dependencies] 20 | brec = { workspace = true, features=["build"] } 21 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/write/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | impl Write for Payload { 6 | fn gen(&self) -> Result { 7 | let payload_name = self.name(); 8 | Ok(quote! { 9 | impl brec::WritePayloadWithHeaderTo for #payload_name {} 10 | }) 11 | } 12 | } 13 | 14 | impl WriteVectored for Payload { 15 | fn gen(&self) -> Result { 16 | let payload_name = self.name(); 17 | Ok(quote! { 18 | impl brec::WriteVectoredPayloadWithHeaderTo for #payload_name {} 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /brec_macros/src/generate/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod parser; 3 | 4 | use crate::Collector; 5 | pub use config::*; 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::quote; 8 | 9 | pub fn generate(cfg: &Config) -> TokenStream { 10 | let mut collector = match Collector::get() { 11 | Ok(collector) => collector, 12 | Err(err) => return syn::Error::new(Span::call_site(), err).into_compile_error(), 13 | }; 14 | if let Err(err) = collector.write(cfg) { 15 | return syn::Error::new(Span::call_site(), err).into_compile_error(); 16 | } 17 | quote! { 18 | include!(concat!(env!("OUT_DIR"), "/brec.rs")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_blob.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockBlob { 7 | blob: [u8; 100], 8 | } 9 | 10 | impl Arbitrary for BlockBlob { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | prop::collection::vec(any::(), 100) 17 | .prop_map(|v| { 18 | let mut blob = [0u8; 100]; 19 | blob.copy_from_slice(&v); 20 | BlockBlob { blob } 21 | }) 22 | .boxed() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /brec_macros/src/modificators/attrs.rs: -------------------------------------------------------------------------------- 1 | use syn::{parse_quote, DeriveInput}; 2 | 3 | pub fn inject_repr_c(input: &mut DeriveInput) -> Result<(), syn::Error> { 4 | let mut has_repr_c = false; 5 | for attr in input.attrs.iter() { 6 | if attr.path().is_ident("repr") { 7 | attr.parse_nested_meta(|meta| { 8 | if let Some(ident) = meta.path.get_ident() { 9 | has_repr_c = &ident.to_string() == "C"; 10 | } 11 | Ok(()) 12 | })?; 13 | } 14 | } 15 | 16 | if !has_repr_c { 17 | input.attrs.insert(0, parse_quote!(#[repr(C)])); 18 | } 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_blob.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockBlob { 7 | blob: [u8; 100], 8 | } 9 | 10 | impl Arbitrary for BlockBlob { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | prop::collection::vec(any::(), 100) 17 | .prop_map(|v| { 18 | let mut blob = [0u8; 100]; 19 | blob.copy_from_slice(&v); 20 | BlockBlob { blob } 21 | }) 22 | .boxed() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /site/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: BRec (Binary Record) 2 | site_url: https://icsmw.github.io/brec/ 3 | docs_dir: docs 4 | theme: 5 | name: material 6 | nav: 7 | - Home: index.md 8 | - Overview: overview.md 9 | - Getting Started: getting_started.md 10 | - Protocol Parts: 11 | - Blocks: parts/blocks.md 12 | - Payloads: parts/payloads.md 13 | - Packets: parts/packets.md 14 | - Code Generation: code_generation.md 15 | - Protocol Tools: 16 | - Buffer: tools/buffer.md 17 | - Storage: tools/storage.md 18 | - Specification: specification.md 19 | - Stability & Performance: 20 | - Tests: stability/tests.md 21 | - Performance: stability/performance.md 22 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/mod.rs: -------------------------------------------------------------------------------- 1 | mod block_bool; 2 | mod block_comb; 3 | mod block_enums; 4 | mod block_f32; 5 | mod block_f64; 6 | mod block_i128; 7 | mod block_i16; 8 | mod block_i32; 9 | mod block_i64; 10 | mod block_i8; 11 | mod block_u128; 12 | mod block_u16; 13 | mod block_u32; 14 | mod block_u64; 15 | mod block_u8; 16 | 17 | pub use block_bool::*; 18 | pub use block_comb::*; 19 | pub use block_enums::*; 20 | pub use block_f32::*; 21 | pub use block_f64::*; 22 | pub use block_i128::*; 23 | pub use block_i16::*; 24 | pub use block_i32::*; 25 | pub use block_i64::*; 26 | pub use block_i8::*; 27 | pub use block_u128::*; 28 | pub use block_u16::*; 29 | pub use block_u32::*; 30 | pub use block_u64::*; 31 | pub use block_u8::*; 32 | -------------------------------------------------------------------------------- /brec/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_crate_dependencies)] 2 | #![doc = include_str!("../README.md")] 3 | 4 | #[cfg(feature = "build")] 5 | pub mod build; 6 | #[cfg(feature = "bincode")] 7 | pub use bincode; 8 | #[cfg(feature = "build")] 9 | pub use build::*; 10 | #[cfg(test)] 11 | pub mod tests; 12 | 13 | extern crate brec_macros; 14 | 15 | pub const MAX_BLOCKS_COUNT: u8 = u8::MAX; 16 | 17 | pub mod error; 18 | pub mod packet; 19 | pub mod payload; 20 | pub mod prelude; 21 | pub mod storage; 22 | pub mod traits; 23 | 24 | pub use brec_macros::*; 25 | pub use crc32fast; 26 | pub use payload::{ 27 | PayloadDecode, PayloadEncode, PayloadEncodeReferred, PayloadHeader, PayloadHooks, 28 | }; 29 | pub use storage::*; 30 | 31 | pub use crate::error::*; 32 | pub use crate::packet::*; 33 | pub use crate::traits::*; 34 | -------------------------------------------------------------------------------- /brec_macros/src/parser/block.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::*; 5 | use std::convert::TryFrom; 6 | 7 | pub fn parse(attrs: BlockAttrs, mut input: DeriveInput) -> TokenStream { 8 | let block = match Block::try_from((attrs, &mut input)) { 9 | Ok(p) => p, 10 | Err(err) => return err.to_compile_error(), 11 | }; 12 | let reflected = match codegen::Gen::gen(&block) { 13 | Ok(p) => p, 14 | Err(err) => { 15 | return syn::Error::new_spanned(&input, err).to_compile_error(); 16 | } 17 | }; 18 | if let Err(err) = modificators::attrs::inject_repr_c(&mut input) { 19 | return syn::Error::new_spanned(&input, err).to_compile_error(); 20 | } 21 | quote! { 22 | #input 23 | 24 | #reflected 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /brec_macros/src/parser/payload.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::*; 5 | use std::convert::TryFrom; 6 | 7 | pub fn parse(attrs: PayloadAttrs, mut input: DeriveInput) -> TokenStream { 8 | let payload = match Payload::try_from((attrs.clone(), &mut input)) { 9 | Ok(p) => p, 10 | Err(err) => return err.to_compile_error(), 11 | }; 12 | let reflected = match codegen::Gen::gen(&payload) { 13 | Ok(p) => p, 14 | Err(err) => { 15 | return syn::Error::new_spanned(&input, err).to_compile_error(); 16 | } 17 | }; 18 | if let Err(err) = modificators::attrs::inject_repr_c(&mut input) { 19 | return syn::Error::new_spanned(&input, err).to_compile_error(); 20 | } 21 | quote! { 22 | #input 23 | 24 | #reflected 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/field/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::convert::TryFrom; 3 | 4 | impl TryFrom<&mut syn::Field> for Field { 5 | type Error = syn::Error; 6 | 7 | fn try_from(field: &mut syn::Field) -> Result { 8 | let Some(name) = field.ident.as_ref() else { 9 | return Err(syn::Error::new_spanned(field, E::FailExtractIdent)); 10 | }; 11 | if Field::is_reserved_name(name.to_string()) { 12 | return Err(syn::Error::new_spanned( 13 | name, 14 | E::ReservedFieldName(name.to_string()), 15 | )); 16 | } 17 | Ok(Self { 18 | name: name.to_string(), 19 | ty: Ty::try_from(&field.ty)?, 20 | injected: false, 21 | vis: Vis::from(&field.vis), 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/wasmjs/src/err.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use wasm_bindgen::JsValue; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ConvertorError { 6 | #[error("Write error {0}")] 7 | WriteError(String), 8 | #[error("Read error {0}")] 9 | ReadError(String), 10 | #[error("Payload header reading: {0}")] 11 | PayloadHeaderReading(String), 12 | #[error("Serialize error: {0}")] 13 | SerializeError(String), 14 | #[error("Serde error: {0}")] 15 | Serde(serde_wasm_bindgen::Error), 16 | } 17 | 18 | impl From for ConvertorError { 19 | fn from(err: serde_wasm_bindgen::Error) -> Self { 20 | Self::Serde(err) 21 | } 22 | } 23 | 24 | impl From for JsValue { 25 | fn from(val: ConvertorError) -> Self { 26 | JsValue::from_str(&val.to_string()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/stress_packets/src/payloads/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod a; 3 | #[cfg(test)] 4 | mod b; 5 | #[cfg(test)] 6 | mod c; 7 | #[cfg(test)] 8 | mod d; 9 | 10 | #[cfg(test)] 11 | pub(crate) use a::*; 12 | #[cfg(test)] 13 | pub(crate) use b::*; 14 | #[cfg(test)] 15 | pub(crate) use c::*; 16 | #[cfg(test)] 17 | pub(crate) use d::*; 18 | 19 | use crate::*; 20 | use proptest::prelude::*; 21 | 22 | impl Arbitrary for Payload { 23 | type Parameters = (); 24 | 25 | type Strategy = BoxedStrategy; 26 | 27 | fn arbitrary_with(_: ()) -> Self::Strategy { 28 | prop_oneof![ 29 | PayloadA::arbitrary().prop_map(Payload::PayloadA), 30 | PayloadB::arbitrary().prop_map(Payload::PayloadB), 31 | PayloadC::arbitrary().prop_map(Payload::PayloadC), 32 | PayloadD::arbitrary().prop_map(Payload::PayloadD), 33 | ] 34 | .boxed() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/wasmjs/src/payloads/a.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[payload(bincode)] 4 | #[derive(serde::Deserialize, serde::Serialize)] 5 | pub struct PayloadA { 6 | pub field_u8: u8, 7 | pub field_u16: u16, 8 | pub field_u32: u32, 9 | pub field_u64: u64, 10 | pub field_u128: u128, 11 | pub field_i8: i8, 12 | pub field_i16: i16, 13 | pub field_i32: i32, 14 | pub field_i64: i64, 15 | pub field_i128: i128, 16 | pub field_f32: f32, 17 | pub field_f64: f64, 18 | pub field_bool: bool, 19 | pub field_str: String, 20 | pub vec_u8: Vec, 21 | pub vec_u16: Vec, 22 | pub vec_u32: Vec, 23 | pub vec_u64: Vec, 24 | pub vec_u128: Vec, 25 | pub vec_i8: Vec, 26 | pub vec_i16: Vec, 27 | pub vec_i32: Vec, 28 | pub vec_i64: Vec, 29 | pub vec_i128: Vec, 30 | pub vec_str: Vec, 31 | } 32 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/read/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | impl Read for Payload { 6 | fn gen(&self) -> Result { 7 | let payload_name = self.name(); 8 | Ok(quote! { 9 | impl brec::ReadPayloadFrom<#payload_name> for #payload_name {} 10 | }) 11 | } 12 | } 13 | 14 | impl TryRead for Payload { 15 | fn gen(&self) -> Result { 16 | let payload_name = self.name(); 17 | Ok(quote! { 18 | impl brec::TryReadPayloadFrom<#payload_name> for #payload_name {} 19 | }) 20 | } 21 | } 22 | 23 | impl TryReadBuffered for Payload { 24 | fn gen(&self) -> Result { 25 | let payload_name = self.name(); 26 | Ok(quote! { 27 | impl brec::TryReadPayloadFromBuffered<#payload_name> for #payload_name {} 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /brec_macros/src/entities/field/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | use crate::*; 4 | 5 | pub(crate) const FIELD_SIG: &str = "__sig"; 6 | pub(crate) const FIELD_CRC: &str = "__crc"; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Field { 10 | pub name: String, 11 | pub ty: Ty, 12 | pub injected: bool, 13 | pub vis: Vis, 14 | } 15 | 16 | impl Field { 17 | pub fn injected>(name: S, ty: Ty) -> Self { 18 | Self { 19 | name: name.as_ref().to_string(), 20 | ty, 21 | injected: true, 22 | vis: Vis::default(), 23 | } 24 | } 25 | pub fn is_reserved_name>(name: S) -> bool { 26 | [FIELD_SIG, FIELD_CRC].contains(&name.as_ref()) 27 | } 28 | pub fn size(&self) -> usize { 29 | self.ty.size() 30 | } 31 | pub fn vis_token(&self) -> Result { 32 | self.vis.as_token() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /brec_macros/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TESTS_DIR="../gen_tests" 4 | 5 | if [ -d "$TESTS_DIR" ]; then 6 | echo "Removing previous tests" 7 | rm -rf "$TESTS_DIR" 8 | fi 9 | 10 | echo "This test may take a significant amount of time, at least several minutes depending on the configuration. In the first stage, crates with a random protocol will be generated, then each crate will be compiled (which takes most of the time) and executed." 11 | read -p "Do you want to continue? (y/n): " answer 12 | 13 | if [[ "$answer" != "y" ]]; then 14 | exit 0 15 | fi 16 | 17 | echo "Generate tests" 18 | 19 | cargo test --release --features generate_macro_test -- --nocapture 20 | 21 | for folder in "$TESTS_DIR"/*/; do 22 | if [ -d "$folder" ]; then 23 | cd "$folder" || { echo "Fail to enter into $folder"; exit 1; } 24 | 25 | echo "Visit $folder" 26 | 27 | cargo run --release 28 | 29 | echo "Test is OK. Executable is:" 30 | 31 | ls -l ./target/release/test_case 32 | 33 | cd - > /dev/null 34 | fi 35 | done -------------------------------------------------------------------------------- /.github/workflows/mkdocs_pages.yml: -------------------------------------------------------------------------------- 1 | name: mkdocs_pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Configure Git Credentials 19 | run: | 20 | git config user.name github-actions[bot] 21 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 22 | 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: 3.x 26 | 27 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 28 | 29 | - uses: actions/cache@v4 30 | with: 31 | key: mkdocs-material-${{ env.cache_id }} 32 | path: .cache 33 | restore-keys: | 34 | mkdocs-material- 35 | 36 | - name: Install dependencies 37 | run: pip install mkdocs-material 38 | 39 | - name: Deploy from ./site/ 40 | run: mkdocs gh-deploy --force --config-file site/mkdocs.yml 41 | -------------------------------------------------------------------------------- /examples/wasmjs/src/payloads/b.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[payload(bincode)] 4 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 5 | pub struct PayloadB { 6 | pub field_u8: Option, 7 | pub field_u16: Option, 8 | pub field_u32: Option, 9 | pub field_u64: Option, 10 | pub field_u128: Option, 11 | pub field_i8: Option, 12 | pub field_i16: Option, 13 | pub field_i32: Option, 14 | pub field_i64: Option, 15 | pub field_i128: Option, 16 | pub field_f32: Option, 17 | pub field_f64: Option, 18 | pub field_bool: Option, 19 | pub field_str: Option, 20 | pub vec_u8: Option>, 21 | pub vec_u16: Option>, 22 | pub vec_u32: Option>, 23 | pub vec_u64: Option>, 24 | pub vec_u128: Option>, 25 | pub vec_i8: Option>, 26 | pub vec_i16: Option>, 27 | pub vec_i32: Option>, 28 | pub vec_i64: Option>, 29 | pub vec_i128: Option>, 30 | pub vec_str: Option>, 31 | } 32 | -------------------------------------------------------------------------------- /brec_macros/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum E { 5 | #[error("Cannot extract identificator")] 6 | FailExtractIdent, 7 | #[error("Cannot parse full path")] 8 | FailParseFullpath, 9 | #[error("Generic types are not supported")] 10 | GenericTypesNotSupported, 11 | #[error("Unsupported type")] 12 | UnsupportedType, 13 | #[error("Unsupported type to use with {0}")] 14 | NotSupportedBy(String), 15 | #[error("Referred types are unsupported")] 16 | ReferenceUnsupported, 17 | #[error("Missed array size")] 18 | MissedArraySize, 19 | #[error("{0} is reserved field name")] 20 | ReservedFieldName(String), 21 | #[error("{0} is unknown visibility")] 22 | FailParseVisibility(String), 23 | #[error("Fail parser derive: {0}")] 24 | FailParseDerive(String), 25 | 26 | #[error("Attribute isn't supported")] 27 | UnsupportedAttr, 28 | 29 | #[error("Fail to access to collector")] 30 | NoAccessToCollector, 31 | 32 | #[error("IO Error: {0}")] 33 | Io(#[from] std::io::Error), 34 | #[error("Var Error: {0}")] 35 | Var(#[from] std::env::VarError), 36 | } 37 | -------------------------------------------------------------------------------- /brec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brec" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "A flexible binary format for storing and streaming structured data as packets with CRC protection and recoverability from corruption. Built for extensibility and robustness." 7 | license = "Apache-2.0" 8 | repository = "https://github.com/icsmw/brec.git" 9 | homepage = "https://github.com/icsmw/brec" 10 | readme = "README.md" 11 | 12 | keywords = [ 13 | "binary", 14 | "packet", 15 | "storage", 16 | "logging", 17 | "streaming" 18 | ] 19 | 20 | categories = [ 21 | "encoding", 22 | "data-structures", 23 | "filesystem", 24 | "development-tools", 25 | "parser-implementations" 26 | ] 27 | 28 | [features] 29 | build = [] 30 | bincode = ["dep:bincode"] 31 | locked_storage = ["fs4", "brec_macros/locked_storage"] 32 | 33 | 34 | [dependencies] 35 | thiserror.workspace = true 36 | crc32fast.workspace = true 37 | enum_ids.workspace = true 38 | brec_macros = { workspace = true, features = [], optional = false } 39 | bincode = { workspace = true, optional = true } 40 | fs4 = { workspace = true, optional = true } 41 | -------------------------------------------------------------------------------- /brec_macros/src/entities/payload/mod.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | 3 | pub(crate) use attr::*; 4 | 5 | use crate::*; 6 | use crc32fast::Hasher; 7 | use proc_macro2::TokenStream; 8 | use quote::{format_ident, quote}; 9 | use syn::Ident; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Payload { 13 | pub name: String, 14 | pub attrs: PayloadAttrs, 15 | pub derives: Derives, 16 | } 17 | 18 | impl Payload { 19 | pub fn new(name: String, attrs: PayloadAttrs, derives: Derives) -> Self { 20 | Self { 21 | name, 22 | attrs, 23 | derives, 24 | } 25 | } 26 | pub fn sig(&self) -> Result { 27 | let mut hasher = Hasher::new(); 28 | hasher.update(self.fullname()?.to_string().as_bytes()); 29 | let sig = hasher.finalize().to_le_bytes(); 30 | Ok(quote! { [#(#sig),*] }) 31 | } 32 | pub fn name(&self) -> Ident { 33 | format_ident!("{}", self.name) 34 | } 35 | pub fn fullname(&self) -> Result { 36 | self.attrs.fullname(self.name()) 37 | } 38 | pub fn fullpath(&self) -> Result { 39 | self.attrs.fullpath(self.name()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /brec_macros/src/generate/config.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::Path; 5 | 6 | #[enum_ids::enum_ids(display_variant_snake)] 7 | #[derive(Debug, Clone)] 8 | pub enum Setting { 9 | NoDefaultPayload, 10 | PayloadsDerive(String), 11 | } 12 | 13 | pub struct Config(pub Vec); 14 | 15 | impl Config { 16 | pub fn is_no_default_payloads(&self) -> bool { 17 | self.0 18 | .iter() 19 | .any(|attr| matches!(attr, Setting::NoDefaultPayload)) 20 | } 21 | pub fn get_payload_derive(&self) -> Result, E> { 22 | let Some(Setting::PayloadsDerive(derives)) = self 23 | .0 24 | .iter() 25 | .find(|attr| matches!(attr, Setting::PayloadsDerive(..))) 26 | else { 27 | return Ok(Vec::new()); 28 | }; 29 | derives 30 | .split(',') 31 | .map(|s| { 32 | let s = s.trim(); 33 | syn::parse_str::(s) 34 | .map(|path| quote!(#path)) 35 | .map_err(|_e| E::FailParseDerive(s.to_owned())) 36 | }) 37 | .collect() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_blobs_max.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct BlockBlobs { 7 | blob_a: [u8; 1], // 1B 8 | blob_b: [u8; 1_024], // 1Kb 9 | blob_c: [u8; 10_240], // 10Kb 10 | } 11 | 12 | impl Arbitrary for BlockBlobs { 13 | type Parameters = (); 14 | 15 | type Strategy = BoxedStrategy; 16 | 17 | fn arbitrary_with(_: ()) -> Self::Strategy { 18 | ( 19 | prop::collection::vec(any::(), 1), 20 | prop::collection::vec(any::(), 1_024), 21 | prop::collection::vec(any::(), 10_240), 22 | ) 23 | .prop_map(|(a, b, c)| { 24 | let mut blob_a = [0u8; 1]; 25 | blob_a.copy_from_slice(&a); 26 | let mut blob_b = [0u8; 1_024]; 27 | blob_b.copy_from_slice(&b); 28 | let mut blob_c = [0u8; 10_240]; 29 | blob_c.copy_from_slice(&c); 30 | BlockBlobs { 31 | blob_a, 32 | blob_b, 33 | blob_c, 34 | } 35 | }) 36 | .boxed() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/to_bytes/field/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote, ToTokens}; 3 | use syn::token::And; 4 | 5 | use crate::*; 6 | 7 | impl ToBytes for Field { 8 | fn to_bytes(&self, blob_by_ref: bool) -> Result { 9 | let name = format_ident!("{}", self.name); 10 | let by_ref = if blob_by_ref { 11 | And::default().into_token_stream() 12 | } else { 13 | TokenStream::new() 14 | }; 15 | match &self.ty { 16 | Ty::U8 => Ok(quote! { &[self.#name] }), 17 | Ty::U16 18 | | Ty::U32 19 | | Ty::U64 20 | | Ty::U128 21 | | Ty::I8 22 | | Ty::I16 23 | | Ty::I32 24 | | Ty::I64 25 | | Ty::I128 26 | | Ty::F32 27 | | Ty::F64 => Ok(quote! { 28 | &self.#name.to_le_bytes() 29 | }), 30 | Ty::Bool => Ok(quote! { 31 | &[self.#name as u8] 32 | }), 33 | Ty::Blob(..) => Ok(quote! { #by_ref self.#name }), 34 | Ty::LinkedToU8(_ident) => Ok(quote! { &[(&self.#name).into()] }), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /brec_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brec_macros" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["d.astafyev@outlook.com"] 6 | description = "Code generator for brec. Implements the block and payload macros." 7 | license = "Apache-2.0" 8 | resolver = "2" 9 | repository = "https://github.com/icsmw/brec.git" 10 | homepage = "https://github.com/icsmw/brec" 11 | readme = "README.md" 12 | 13 | keywords = [ 14 | "binary", 15 | "packet", 16 | "storage", 17 | "logging", 18 | "streaming" 19 | ] 20 | 21 | categories = [ 22 | "encoding", 23 | "data-structures", 24 | "filesystem", 25 | "development-tools", 26 | "parser-implementations" 27 | ] 28 | 29 | [lib] 30 | proc-macro = true 31 | 32 | [features] 33 | generate_macro_test = [] 34 | locked_storage = [] 35 | 36 | [dependencies] 37 | thiserror.workspace = true 38 | syn.workspace = true 39 | quote.workspace = true 40 | proc-macro2.workspace = true 41 | enum_ids.workspace = true 42 | crc32fast.workspace = true 43 | lazy_static.workspace = true 44 | 45 | [dev-dependencies] 46 | proptest.workspace = true 47 | quote.workspace = true 48 | proc-macro2.workspace = true 49 | uuid = { workspace = true , features = ["fast-rng", "macro-diagnostics", "v4"] } 50 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_blobs_max.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[block] 5 | #[derive(PartialEq, PartialOrd, Debug, Clone)] 6 | pub struct BlockBlobs { 7 | blob_a: [u8; 1], // 1B 8 | blob_b: [u8; 1_024], // 1Kb 9 | blob_c: [u8; 10_240], // 10Kb 10 | } 11 | 12 | impl Arbitrary for BlockBlobs { 13 | type Parameters = (); 14 | 15 | type Strategy = BoxedStrategy; 16 | 17 | fn arbitrary_with(_: ()) -> Self::Strategy { 18 | ( 19 | prop::collection::vec(any::(), 1), 20 | prop::collection::vec(any::(), 1_024), 21 | prop::collection::vec(any::(), 10_240), 22 | ) 23 | .prop_map(|(a, b, c)| { 24 | let mut blob_a = [0u8; 1]; 25 | blob_a.copy_from_slice(&a); 26 | let mut blob_b = [0u8; 1_024]; 27 | blob_b.copy_from_slice(&b); 28 | let mut blob_c = [0u8; 10_240]; 29 | blob_c.copy_from_slice(&c); 30 | BlockBlobs { 31 | blob_a, 32 | blob_b, 33 | blob_c, 34 | } 35 | }) 36 | .boxed() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /brec_macros/src/collector/payloads/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn gen( 7 | payloads: &[&Payload], 8 | derives: Vec, 9 | cfg: &Config, 10 | ) -> Result { 11 | let mut variants = Vec::new(); 12 | for pl in payloads.iter() { 13 | let fullname = pl.fullname()?; 14 | let fullpath = pl.fullpath()?; 15 | variants.push(quote! {#fullname(#fullpath)}); 16 | } 17 | let derives = [derives, cfg.get_payload_derive()?].concat(); 18 | let derives = if derives.is_empty() { 19 | quote! {} 20 | } else { 21 | quote! {#[derive(#(#derives,)*)]} 22 | }; 23 | let deafults = if cfg.is_no_default_payloads() { 24 | quote! {} 25 | } else { 26 | quote! { 27 | Bytes(Vec), 28 | String(String), 29 | } 30 | }; 31 | Ok(quote! { 32 | #derives 33 | #[allow(non_snake_case)] 34 | pub enum Payload { 35 | #(#variants,)* 36 | #deafults 37 | } 38 | 39 | impl brec::PayloadHooks for Payload {} 40 | 41 | impl brec::PayloadInnerDef for Payload {} 42 | 43 | impl brec::PayloadDef for Payload {} 44 | 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /brec_macros/src/collector/packet.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::*; 5 | 6 | pub fn gen() -> Result { 7 | let locked_storage = if cfg!(feature = "locked_storage") { 8 | quote! { 9 | #[allow(dead_code)] 10 | pub type FileStorage<'a> = brec::FileStorageDef, Payload, Payload>; 11 | } 12 | } else { 13 | quote! {} 14 | }; 15 | Ok(quote! { 16 | #[allow(dead_code)] 17 | pub type Packet = brec::PacketDef; 18 | 19 | #[allow(dead_code)] 20 | pub type PacketBufReader<'a, R> = 21 | brec::PacketBufReaderDef<'a, R, Block, BlockReferred<'a>, Payload, Payload>; 22 | 23 | #[allow(dead_code)] 24 | pub type Rules<'a> = brec::RulesDef, Payload, Payload>; 25 | 26 | #[allow(dead_code)] 27 | pub type Rule<'a> = brec::RuleDef, Payload, Payload>; 28 | 29 | #[allow(dead_code)] 30 | pub type RuleFnDef = brec::RuleFnDef; 31 | 32 | #[allow(dead_code)] 33 | pub type Storage<'a, S> = brec::StorageDef, Payload, Payload>; 34 | 35 | #locked_storage 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /brec_macros/src/entities/vis/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{token, Visibility}; 4 | 5 | use crate::*; 6 | 7 | #[derive(Debug, Clone, Default)] 8 | pub enum Vis { 9 | Public, 10 | #[default] 11 | Private, 12 | Restricted(String), 13 | } 14 | 15 | impl Vis { 16 | pub fn as_token(&self) -> Result { 17 | Ok(match self { 18 | Self::Public => token::Pub::default().into_token_stream(), 19 | Self::Private => TokenStream::new(), 20 | Self::Restricted(rstr) => { 21 | let vis: Visibility = 22 | syn::parse_str(rstr).map_err(|_| E::FailParseVisibility(rstr.to_owned()))?; 23 | quote! { #vis } 24 | } 25 | }) 26 | } 27 | } 28 | 29 | impl From<&DeriveInput> for Vis { 30 | fn from(value: &DeriveInput) -> Self { 31 | (&value.vis).into() 32 | } 33 | } 34 | 35 | impl From<&Visibility> for Vis { 36 | fn from(vis: &Visibility) -> Self { 37 | match vis { 38 | Visibility::Public(..) => Vis::Public, 39 | Visibility::Inherited => Vis::Private, 40 | Visibility::Restricted(rstr) => Vis::Restricted(rstr.to_token_stream().to_string()), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /brec_macros/src/collector/payloads/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::*; 5 | 6 | mod enums; 7 | mod props; 8 | mod read; 9 | mod write; 10 | 11 | pub fn gen(payloads: Vec<&Payload>, cfg: &Config) -> Result { 12 | let derives = Derives::common(payloads.iter().map(|p| &p.derives).collect())?; 13 | let payload = enums::gen(&payloads, derives, cfg)?; 14 | let encode = props::encode(&payloads)?; 15 | let encode_referred = props::encode_referred(&payloads)?; 16 | let sig = props::sig(&payloads)?; 17 | let crc = props::crc(&payloads)?; 18 | let size = props::size(&payloads)?; 19 | let extract_from = read::extract_from(&payloads)?; 20 | let try_extract_from = read::try_extract_from(&payloads)?; 21 | let try_extract_from_buffered = read::try_extract_from_buffered(&payloads)?; 22 | let writing_to = write::writing_to(&payloads)?; 23 | let writing_vectored_to = write::writing_vectored_to(&payloads)?; 24 | Ok(quote! { 25 | #payload 26 | #encode 27 | #encode_referred 28 | #sig 29 | #crc 30 | #size 31 | #extract_from 32 | #try_extract_from 33 | #try_extract_from_buffered 34 | #writing_to 35 | #writing_vectored_to 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /site/docs/stability/tests.md: -------------------------------------------------------------------------------- 1 | 2 | The stability of `brec` is ensured through two levels of testing. 3 | 4 | ## Functional Testing 5 | 6 | To test reading, writing, parsing, filtering, and other functions, `brec` uses `proptest` to generate **random values** for predefined (structurally consistent) blocks and payloads. Blocks and payloads are tested **both separately and in combination** as part of packet testing. 7 | 8 | Packets are constructed with **randomly generated blocks and payloads**. Additionally, the ability of `brec` tools to **reliably read and write randomly generated blocks** is also tested, specifically focusing on `Storage` and `PacketBufReader`. 9 | 10 | In total, **over 40 GB of test data** is generated for this type of testing. 11 | 12 | ## Macro Testing 13 | 14 | To validate the behavior of the `block` and `payload` macros, `brec` also uses `proptest`, but this time it **not only generates random data but also randomly constructs block and payload structures**. 15 | 16 | Each randomly generated set of structures is saved as a separate crate. After generating these test cases, each one is **compiled and executed** to ensure stability. Specifically, all randomly generated packets **must be successfully encoded and subsequently decoded without errors**. -------------------------------------------------------------------------------- /brec_macros/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "importer" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "proc-macro2", 10 | "quote", 11 | "syn", 12 | ] 13 | 14 | [[package]] 15 | name = "proc-macro2" 16 | version = "1.0.78" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 19 | dependencies = [ 20 | "unicode-ident", 21 | ] 22 | 23 | [[package]] 24 | name = "quote" 25 | version = "1.0.35" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 28 | dependencies = [ 29 | "proc-macro2", 30 | ] 31 | 32 | [[package]] 33 | name = "syn" 34 | version = "2.0.52" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "unicode-ident", 41 | ] 42 | 43 | [[package]] 44 | name = "unicode-ident" 45 | version = "1.0.12" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 48 | -------------------------------------------------------------------------------- /brec_macros/src/collector/blocks/enums.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn gen(blocks: &[&Block], derives: Vec, _cfg: &Config) -> Result { 7 | let mut variants = Vec::new(); 8 | for blk in blocks.iter() { 9 | let fullname = blk.fullname()?; 10 | let fullpath = blk.fullpath()?; 11 | variants.push(quote! {#fullname(#fullpath)}); 12 | } 13 | let derives = if derives.is_empty() { 14 | quote! {} 15 | } else { 16 | quote! {#[derive(#(#derives,)*)]} 17 | }; 18 | Ok(quote! { 19 | #derives 20 | #[allow(non_snake_case)] 21 | pub enum Block { 22 | #(#variants,)* 23 | } 24 | 25 | impl brec::BlockDef for Block {} 26 | }) 27 | } 28 | 29 | pub fn gen_referred(blocks: &[&Block]) -> Result { 30 | let mut variants = Vec::new(); 31 | for blk in blocks.iter() { 32 | let fullname = blk.fullname()?; 33 | let referred_name = blk.referred_name(); 34 | variants.push(quote! {#fullname(#referred_name<'a>)}); 35 | } 36 | Ok(quote! { 37 | pub enum BlockReferred<'a> { 38 | #(#variants,)* 39 | } 40 | 41 | impl<'a> brec::BlockReferredDef for BlockReferred<'a> {} 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/payload/mod.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | 3 | use crate::*; 4 | use std::convert::TryFrom; 5 | use syn::{Data, DeriveInput}; 6 | 7 | pub const PAYLOAD_ATTR: &str = "payload"; 8 | 9 | impl TryFrom<(PayloadAttrs, &mut DeriveInput)> for Payload { 10 | type Error = syn::Error; 11 | fn try_from((attrs, input): (PayloadAttrs, &mut DeriveInput)) -> Result { 12 | let name = &input.ident; 13 | if !input.generics.params.is_empty() { 14 | return Err(syn::Error::new_spanned( 15 | &input.generics, 16 | E::GenericTypesNotSupported, 17 | )); 18 | } 19 | input 20 | .attrs 21 | .retain(|attr| !attr.path().is_ident(PAYLOAD_ATTR)); 22 | if !matches!(&input.data, Data::Struct(..) | Data::Enum(..)) { 23 | return Err(syn::Error::new_spanned( 24 | &input, 25 | E::NotSupportedBy(PAYLOAD_ATTR.to_string()), 26 | )); 27 | } 28 | let payload = Self::new(name.to_string(), attrs, (&*input).into()); 29 | Collector::get() 30 | .map_err(|err| syn::Error::new_spanned(&input, err))? 31 | .add_payload(payload.clone()) 32 | .map_err(|err| syn::Error::new_spanned(&input, err))?; 33 | Ok(payload) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /brec/src/traits/read/status.rs: -------------------------------------------------------------------------------- 1 | /// Result type representing the outcome of a read attempt. 2 | /// 3 | /// `ReadStatus` allows distinguishing between successful reads and cases 4 | /// where more data is required to complete the read. 5 | pub enum ReadStatus { 6 | /// The read operation succeeded and produced a value of type `T`. 7 | Success(T), 8 | 9 | /// The read operation failed due to insufficient data. 10 | /// 11 | /// Contains the number of additional bytes required to complete the read. 12 | NotEnoughData(u64), 13 | } 14 | 15 | impl ReadStatus { 16 | /// Maps the inner `Success` value using the provided function. 17 | /// 18 | /// If the status is `Success`, applies `mapper` to the inner value and returns 19 | /// a new `ReadStatus::Success`. If the status is `NotEnoughData`, it is returned unchanged. 20 | /// 21 | /// # Arguments 22 | /// * `mapper` – A function to apply to the `Success` value. 23 | /// 24 | /// # Returns 25 | /// A new `ReadStatus` with the mapped type. 26 | pub fn map(self, mapper: F) -> ReadStatus 27 | where 28 | F: FnOnce(T) -> K, 29 | { 30 | match self { 31 | ReadStatus::Success(value) => ReadStatus::Success(mapper(value)), 32 | ReadStatus::NotEnoughData(n) => ReadStatus::NotEnoughData(n), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/on_pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | linting: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Install Rust toolchains 18 | run: | 19 | rustup update 20 | rustup install nightly 21 | rustup component add clippy --toolchain nightly 22 | - name: Make scripts executable 23 | run: chmod +x ./lint.sh 24 | - name: Linting nightly 25 | run: sh ./lint.sh 26 | 27 | tests: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Update rust 33 | run: rustup update 34 | - name: Make scripts executable 35 | run: chmod +x ./test.sh 36 | - name: Tests 37 | run: sh ./test.sh 38 | 39 | examples: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v3 44 | - name: Update rust 45 | run: rustup update 46 | - name: Make scripts executable 47 | run: chmod +x ./build.sh 48 | working-directory: ./examples 49 | - name: Tests 50 | run: sh ./build.sh 51 | working-directory: ./examples 52 | -------------------------------------------------------------------------------- /tests/stress_payloads/src/d.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[payload(bincode)] 5 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 6 | pub enum PayloadD { 7 | U8(u8), 8 | U16(u16), 9 | U32(u32), 10 | U64(u64), 11 | U128(u128), 12 | I8(i8), 13 | I16(i16), 14 | I32(i32), 15 | I64(i64), 16 | I128(i128), 17 | String(String), 18 | } 19 | 20 | impl Arbitrary for PayloadD { 21 | type Parameters = (); 22 | 23 | type Strategy = BoxedStrategy; 24 | 25 | fn arbitrary_with(_: ()) -> Self::Strategy { 26 | prop_oneof![ 27 | proptest::num::u8::ANY.prop_map(PayloadD::U8), 28 | proptest::num::u16::ANY.prop_map(PayloadD::U16), 29 | proptest::num::u32::ANY.prop_map(PayloadD::U32), 30 | proptest::num::u64::ANY.prop_map(PayloadD::U64), 31 | proptest::num::u128::ANY.prop_map(PayloadD::U128), 32 | proptest::num::i8::ANY.prop_map(PayloadD::I8), 33 | proptest::num::i16::ANY.prop_map(PayloadD::I16), 34 | proptest::num::i32::ANY.prop_map(PayloadD::I32), 35 | proptest::num::i64::ANY.prop_map(PayloadD::I64), 36 | proptest::num::i128::ANY.prop_map(PayloadD::I128), 37 | any::().prop_map(PayloadD::String) 38 | ] 39 | .boxed() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /brec_macros/src/collector/blocks/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::*; 5 | 6 | mod enums; 7 | mod props; 8 | mod read; 9 | mod write; 10 | 11 | pub fn gen(blocks: Vec<&Block>, cfg: &Config) -> Result { 12 | let derives = Derives::common(blocks.iter().map(|b| &b.derives).collect())?; 13 | let block = enums::gen(&blocks, derives, cfg)?; 14 | let block_referred = enums::gen_referred(&blocks)?; 15 | let prop = props::gen(&blocks)?; 16 | let prop_referred = props::gen_referred(&blocks)?; 17 | let referred_into = props::referred_into(&blocks)?; 18 | let read_from = read::read_from(&blocks)?; 19 | let read_block_from = read::read_block_from(&blocks)?; 20 | let read_from_slice = read::read_from_slice(&blocks)?; 21 | let try_read_from = read::try_read_from(&blocks)?; 22 | let try_read_from_buffered = read::try_read_from_buffered(&blocks)?; 23 | let write_to = write::write_to(&blocks)?; 24 | let write_vectored_to = write::write_vectored_to(&blocks)?; 25 | Ok(quote! { 26 | #block 27 | #block_referred 28 | #prop 29 | #prop_referred 30 | #referred_into 31 | #read_from 32 | #read_block_from 33 | #read_from_slice 34 | #try_read_from 35 | #try_read_from_buffered 36 | #write_to 37 | #write_vectored_to 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /tests/stress_packets/src/payloads/d.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[payload(bincode)] 5 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 6 | pub enum PayloadD { 7 | U8(u8), 8 | U16(u16), 9 | U32(u32), 10 | U64(u64), 11 | U128(u128), 12 | I8(i8), 13 | I16(i16), 14 | I32(i32), 15 | I64(i64), 16 | I128(i128), 17 | String(String), 18 | } 19 | 20 | impl Arbitrary for PayloadD { 21 | type Parameters = (); 22 | 23 | type Strategy = BoxedStrategy; 24 | 25 | fn arbitrary_with(_: ()) -> Self::Strategy { 26 | prop_oneof![ 27 | proptest::num::u8::ANY.prop_map(PayloadD::U8), 28 | proptest::num::u16::ANY.prop_map(PayloadD::U16), 29 | proptest::num::u32::ANY.prop_map(PayloadD::U32), 30 | proptest::num::u64::ANY.prop_map(PayloadD::U64), 31 | proptest::num::u128::ANY.prop_map(PayloadD::U128), 32 | proptest::num::i8::ANY.prop_map(PayloadD::I8), 33 | proptest::num::i16::ANY.prop_map(PayloadD::I16), 34 | proptest::num::i32::ANY.prop_map(PayloadD::I32), 35 | proptest::num::i64::ANY.prop_map(PayloadD::I64), 36 | proptest::num::i128::ANY.prop_map(PayloadD::I128), 37 | any::().prop_map(PayloadD::String) 38 | ] 39 | .boxed() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /brec/src/storage/slot/write.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Serializes a `Slot` into a contiguous byte buffer. 4 | /// 5 | /// The buffer layout includes: 6 | /// - [8 bytes] signature (`STORAGE_SLOT_SIG`) 7 | /// - [8 bytes] capacity 8 | /// - [N × 8 bytes] lengths (each as `u64`, where N = `capacity`) 9 | /// - [4 bytes] CRC 10 | fn get_buffer(slot: &Slot) -> Vec { 11 | let mut buffer = vec![0u8; slot.size() as usize]; 12 | let mut offset = 0; 13 | buffer[offset..offset + 8usize].copy_from_slice(&STORAGE_SLOT_SIG); 14 | offset += 8usize; 15 | buffer[offset..offset + 8usize].copy_from_slice(&slot.capacity.to_le_bytes()); 16 | offset += 8usize; 17 | for lenght in slot.lenghts.iter() { 18 | buffer[offset..offset + 8usize].copy_from_slice(&lenght.to_le_bytes()); 19 | offset += 8usize; 20 | } 21 | buffer[offset..offset + 4usize].copy_from_slice(&slot.crc); 22 | buffer 23 | } 24 | 25 | impl WriteTo for Slot { 26 | /// Writes the serialized `Slot` to the given writer. May write partially. 27 | fn write(&self, buf: &mut T) -> std::io::Result { 28 | buf.write(&get_buffer(self)) 29 | } 30 | 31 | /// Writes the entire serialized `Slot` to the writer. Will retry until complete. 32 | fn write_all(&self, buf: &mut T) -> std::io::Result<()> { 33 | buf.write_all(&get_buffer(self)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /site/docs/overview.md: -------------------------------------------------------------------------------- 1 | 2 | The primary unit of information in `brec` is a packet (`Packet`) — a ready-to-transmit message with a unique signature (allowing it to be recognized within mixed data) and a CRC to ensure data integrity. 3 | 4 | A packet consists of a set of blocks (`Block`) and, optionally, a payload (`Payload`). 5 | 6 | ![Scheme](assets/scheme.svg) 7 | 8 | Blocks (`Block`) are the minimal units of information in the `brec` system. A block can contain only primitives, such as numbers, boolean values, and byte slices. A block serves as a kind of packet index, allowing for quick determination of whether a packet requires full processing (i.e., parsing the `Payload`) or can be ignored. 9 | 10 | The payload (`Payload`) is an optional part of the packet. Unlike blocks (`Block`), it has no restrictions on the type of data it can contain—it can be a `struct` or `enum` of any complexity and nesting level. 11 | 12 | Unlike most protocols, `brec` does not require users to define a fixed set of messages but does require them to describe blocks (`Block`) and payload data (`Payload`). 13 | 14 | Users can construct packets (messages) by combining various sets of blocks and payloads. This means `brec` does not impose a predefined list of packets (`Packet`) within the protocol but allows them to be defined dynamically. As a result, the same block and/or payload can be used across multiple packets (messages) without any restrictions. 15 | -------------------------------------------------------------------------------- /examples/wasmjs/src/payloads/c.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 4 | pub struct NestedStructCA { 5 | pub field_u8: u8, 6 | pub field_u16: u16, 7 | pub field_u32: u32, 8 | } 9 | 10 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 11 | pub struct NestedStructCB { 12 | pub field_i8: i8, 13 | pub field_i16: i16, 14 | pub field_i32: i32, 15 | } 16 | 17 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 18 | pub struct NestedStructCC { 19 | pub field_bool: bool, 20 | pub field_str: String, 21 | pub vec_u8: Vec, 22 | } 23 | 24 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 25 | pub enum NestedEnumC { 26 | One(String), 27 | Two(Vec), 28 | Three, 29 | Four(NestedStructCC), 30 | } 31 | 32 | #[payload(bincode)] 33 | #[derive(serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Debug, Clone)] 34 | pub struct PayloadC { 35 | pub field_u8: u8, 36 | pub field_u16: u16, 37 | pub field_u32: u32, 38 | pub field_u64: u64, 39 | pub field_u128: u128, 40 | pub field_struct_a: NestedStructCA, 41 | pub field_struct_b: Option, 42 | pub field_struct_c: Vec, 43 | pub field_enum: NestedEnumC, 44 | pub vec_enum: Vec, 45 | } 46 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/from_bytes/ty/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::Ident; 4 | 5 | use crate::*; 6 | 7 | impl FromBytes for Ty { 8 | fn safe(&self, src: &Ident, from: usize, to: usize) -> TokenStream { 9 | match self { 10 | Ty::U8 11 | | Ty::U16 12 | | Ty::U32 13 | | Ty::U64 14 | | Ty::U128 15 | | Ty::I8 16 | | Ty::I16 17 | | Ty::I32 18 | | Ty::I64 19 | | Ty::I128 20 | | Ty::F32 21 | | Ty::F64 => { 22 | let ty = self.direct(); 23 | quote! { 24 | #ty::from_le_bytes(#src[#from..#to].try_into()?) 25 | } 26 | } 27 | Ty::Bool => { 28 | quote! { 29 | u8::from_le_bytes(#src[#from..#to].try_into()?) == 1 30 | } 31 | } 32 | Ty::Blob(len) => quote! { 33 | <&[u8; #len]>::try_from(&#src[#from..#to])? 34 | }, 35 | Ty::LinkedToU8(enum_name) => { 36 | let ident = self.direct(); 37 | quote! { 38 | #ident::try_from(u8::from_le_bytes(#src[#from..#to].try_into()?)) 39 | .map_err(|err| brec::Error::FailedConverting(#enum_name.to_owned(), err))? 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod block_blob; 3 | #[cfg(test)] 4 | mod block_blobs_max; 5 | #[cfg(test)] 6 | mod block_bool; 7 | #[cfg(test)] 8 | mod block_comb; 9 | #[cfg(test)] 10 | mod block_enums; 11 | #[cfg(test)] 12 | mod block_f32; 13 | #[cfg(test)] 14 | mod block_f64; 15 | #[cfg(test)] 16 | mod block_i128; 17 | #[cfg(test)] 18 | mod block_i16; 19 | #[cfg(test)] 20 | mod block_i32; 21 | #[cfg(test)] 22 | mod block_i64; 23 | #[cfg(test)] 24 | mod block_i8; 25 | #[cfg(test)] 26 | mod block_u128; 27 | #[cfg(test)] 28 | mod block_u16; 29 | #[cfg(test)] 30 | mod block_u32; 31 | #[cfg(test)] 32 | mod block_u64; 33 | #[cfg(test)] 34 | mod block_u8; 35 | 36 | #[cfg(test)] 37 | pub(crate) use block_blob::*; 38 | #[cfg(test)] 39 | pub(crate) use block_blobs_max::*; 40 | #[cfg(test)] 41 | pub(crate) use block_bool::*; 42 | #[cfg(test)] 43 | pub(crate) use block_comb::*; 44 | #[cfg(test)] 45 | pub(crate) use block_enums::*; 46 | #[cfg(test)] 47 | pub(crate) use block_f32::*; 48 | #[cfg(test)] 49 | pub(crate) use block_f64::*; 50 | #[cfg(test)] 51 | pub(crate) use block_i128::*; 52 | #[cfg(test)] 53 | pub(crate) use block_i16::*; 54 | #[cfg(test)] 55 | pub(crate) use block_i32::*; 56 | #[cfg(test)] 57 | pub(crate) use block_i64::*; 58 | #[cfg(test)] 59 | pub(crate) use block_i8::*; 60 | #[cfg(test)] 61 | pub(crate) use block_u128::*; 62 | #[cfg(test)] 63 | pub(crate) use block_u16::*; 64 | #[cfg(test)] 65 | pub(crate) use block_u32::*; 66 | #[cfg(test)] 67 | pub(crate) use block_u64::*; 68 | #[cfg(test)] 69 | pub(crate) use block_u8::*; 70 | 71 | #[cfg(test)] 72 | mod test; 73 | 74 | fn main() {} 75 | -------------------------------------------------------------------------------- /brec_macros/src/tests/field.rs: -------------------------------------------------------------------------------- 1 | use crate::tests::*; 2 | use proptest::prelude::*; 3 | use quote::{format_ident, quote}; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct Field { 7 | pub name: String, 8 | pub value: Value, 9 | } 10 | 11 | impl Field { 12 | pub fn is_ordered_ty(&self) -> bool { 13 | self.value.is_ordered_ty() 14 | } 15 | } 16 | 17 | impl Arbitrary for Field { 18 | type Parameters = (Target, u8); 19 | 20 | type Strategy = BoxedStrategy; 21 | 22 | fn arbitrary_with((owner, deep): (Target, u8)) -> Self::Strategy { 23 | if matches!(owner, Target::Block) { 24 | Target::block_values() 25 | } else if deep > MAX_VALUE_DEEP { 26 | Target::nested_values() 27 | } else { 28 | Target::payload_values() 29 | } 30 | .prop_flat_map(move |id| { 31 | Value::arbitrary_with((id, deep + 1)).prop_map(move |value: Value| Field { 32 | name: gen_name(false), 33 | value, 34 | }) 35 | }) 36 | .boxed() 37 | } 38 | } 39 | 40 | impl Generate for Field { 41 | type Options = (); 42 | 43 | fn declaration(&self, _: Self::Options) -> TokenStream { 44 | let name = format_ident!("{}", self.name); 45 | let ty = self.value.declaration(()); 46 | quote! { 47 | #name: #ty 48 | } 49 | } 50 | fn instance(&self, _: Self::Options) -> TokenStream { 51 | let name = format_ident!("{}", self.name); 52 | let vl = self.value.instance(()); 53 | quote! { 54 | #name: #vl 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0 (28.06.2025) 2 | 3 | ### Added 4 | 5 | - Feature `locked_storage`: introduces `FileStorage`, a file-backed wrapper around `StorageDef` with advisory locking via `.lock` files to coordinate exclusive access across processes. Enabled through the `fs4` crate and supports configurable timeout and retry interval. 6 | 7 | ## 0.1.4 (21.06.2025) 8 | 9 | ### Changes 10 | 11 | Full compatibility with tokio::spawn and other `Send + 'static` async environments: 12 | 13 | - All internal callback types (`RuleFnDef`) now explicitly implement `Send + 'static`. 14 | - `StorageDef` and all related structures are now `Send`, making them safe to use inside async tasks and multi-threaded executors. 15 | 16 | This enables direct integration of `brec` into tokio-based systems without additional wrappers. 17 | 18 | This change does not make `brec` asynchronous — I/O operations remain blocking (`std::fs`). However, it is now safe to use `StorageDef` in asynchronous environments with care (e.g., inside `tokio::task::block_in_place` or `spawn_blocking`). 19 | 20 | ## 0.1.3 (28.03.2025) 21 | 22 | ### Fixes 23 | 24 | - Fixed issue with `count()` on `StorageDef` 25 | - Fixed missed setup (on load) of slot's locator 26 | 27 | ### Changes 28 | 29 | - Added tests for `count()` on `StorageDef` 30 | - Updated documentation 31 | 32 | ## 0.1.2 (27.03.2025) 33 | 34 | ### Fixes 35 | 36 | - Consider full path of derives on blocks and payloads parsing 37 | 38 | ### Changes 39 | 40 | - Update documentation 41 | - Add wasm example 42 | - Include examples build into CI pipeline 43 | 44 | ## 0.1.1 (24.03.2025) 45 | 46 | ### Changes 47 | 48 | - Update documentation -------------------------------------------------------------------------------- /brec_macros/src/collector/blocks/write.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn write_to(blocks: &[&Block]) -> Result { 7 | let mut write = Vec::new(); 8 | let mut write_all = Vec::new(); 9 | for blk in blocks.iter() { 10 | let fullname = blk.fullname()?; 11 | write.push(quote! { 12 | Block::#fullname(blk) => blk.write(buf) 13 | }); 14 | write_all.push(quote! { 15 | Block::#fullname(blk) => blk.write_all(buf) 16 | }); 17 | } 18 | Ok(quote! { 19 | impl brec::WriteTo for Block { 20 | fn write(&self, buf: &mut T) -> std::io::Result { 21 | match self { 22 | #(#write,)* 23 | } 24 | } 25 | fn write_all(&self, buf: &mut T) -> std::io::Result<()> { 26 | match self { 27 | #(#write_all,)* 28 | } 29 | } 30 | } 31 | }) 32 | } 33 | 34 | pub fn write_vectored_to(blocks: &[&Block]) -> Result { 35 | let mut slices = Vec::new(); 36 | for blk in blocks.iter() { 37 | let fullname = blk.fullname()?; 38 | slices.push(quote! { 39 | Block::#fullname(blk) => blk.slices() 40 | }); 41 | } 42 | Ok(quote! { 43 | impl brec::WriteVectoredTo for Block { 44 | fn slices(&self) -> std::io::Result { 45 | match self { 46 | #(#slices,)* 47 | } 48 | } 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /tests/locked_storage/src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proptest::prelude::*; 3 | use serial_test::serial; 4 | 5 | pub const MATCH: &str = "-match-"; 6 | 7 | brec::generate!(payloads_derive = "Clone, Debug"); 8 | 9 | #[derive(Debug)] 10 | pub struct WrappedPacket { 11 | blocks: Vec, 12 | payload: Option, 13 | } 14 | 15 | impl From<&WrappedPacket> for Packet { 16 | fn from(wrapped: &WrappedPacket) -> Self { 17 | Packet::new(wrapped.blocks.clone(), wrapped.payload.clone()) 18 | } 19 | } 20 | 21 | impl From for WrappedPacket { 22 | fn from(pkg: Packet) -> Self { 23 | WrappedPacket { 24 | blocks: pkg.blocks, 25 | payload: pkg.payload, 26 | } 27 | } 28 | } 29 | 30 | impl Arbitrary for WrappedPacket { 31 | type Parameters = (); 32 | 33 | type Strategy = BoxedStrategy; 34 | 35 | fn arbitrary_with(_: ()) -> Self::Strategy { 36 | any::() 37 | .prop_map(|record| WrappedPacket { 38 | blocks: vec![Block::Metadata(record.mt)], 39 | payload: Some(Payload::String(record.msg)), 40 | }) 41 | .boxed() 42 | } 43 | } 44 | 45 | const BIN_LOG_FILE: &str = "locked_storage_test_measurements.bin"; 46 | 47 | proptest! { 48 | #![proptest_config(ProptestConfig { 49 | max_shrink_iters: 50, 50 | ..ProptestConfig::with_cases(1) 51 | })] 52 | 53 | #[test] 54 | #[serial] 55 | fn bin_logs(rows in proptest::collection::vec(any::(), 10)) { 56 | storage::create_file(rows, 100, BIN_LOG_FILE)?; 57 | storage::read_file(BIN_LOG_FILE)?; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/locked_storage/src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::test::*; 2 | use std::time::Duration; 3 | 4 | pub fn create_file( 5 | packets: Vec, 6 | mut count: usize, 7 | filename: &str, 8 | ) -> std::io::Result<()> { 9 | let tmp = std::env::temp_dir().join(filename); 10 | if tmp.exists() { 11 | return Ok(()); 12 | } 13 | let mut storage = FileStorage::new(tmp, Some(Duration::from_millis(400)), None) 14 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string()))?; 15 | while count > 0 { 16 | for packet in packets.iter() { 17 | storage.insert(packet.into()).map_err(|err| { 18 | std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string()) 19 | })?; 20 | } 21 | count -= 1; 22 | } 23 | Ok(()) 24 | } 25 | 26 | pub fn read_file(filename: &str) -> std::io::Result<()> { 27 | let tmp = std::env::temp_dir().join(filename); 28 | let mut storage = FileStorage::new(tmp, Some(Duration::from_millis(400)), None) 29 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string()))?; 30 | let mut count = 0; 31 | for packet in storage.iter() { 32 | match packet { 33 | Ok(_packet) => { 34 | count += 1; 35 | } 36 | Err(err) => { 37 | panic!("Fail to read storage: {err}"); 38 | } 39 | } 40 | } 41 | if count != storage.count() { 42 | return Err(std::io::Error::new( 43 | std::io::ErrorKind::Other, 44 | format!("Dismatch lengths: {} vs {count}", storage.count()), 45 | )); 46 | } 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/props/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | impl Crc for Payload { 6 | fn gen(&self) -> Result { 7 | let payload_name = self.name(); 8 | Ok( 9 | if self.attrs.is_bincode() && !self.attrs.is_no_auto_crc() && !self.attrs.is_no_crc() { 10 | quote! { 11 | impl brec::PayloadCrc for #payload_name {} 12 | } 13 | } else if self.attrs.is_bincode() && self.attrs.is_no_crc() { 14 | quote! { 15 | impl brec::PayloadCrc for #payload_name { 16 | fn crc(&self) -> std::io::Result { 17 | Ok(brec::ByteBlock::Len4([0,0,0,0])) 18 | } 19 | fn crc_size() -> usize { 20 | 4 21 | } 22 | } 23 | } 24 | } else { 25 | quote! {} 26 | }, 27 | ) 28 | } 29 | } 30 | 31 | impl Size for Payload { 32 | fn gen(&self) -> TokenStream { 33 | let payload_name = self.name(); 34 | if self.attrs.is_bincode() { 35 | quote! { 36 | impl brec::PayloadSize for #payload_name { 37 | fn size(&self) -> std::io::Result { 38 | brec::bincode::serialized_size(self) 39 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) 40 | } 41 | } 42 | } 43 | } else { 44 | quote! {} 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/measurements/src/content.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::*; 2 | 3 | use crate::{test::MATCH, Level, Metadata, Target}; 4 | 5 | #[derive(Debug)] 6 | pub struct TextualRow { 7 | pub msg: String, 8 | } 9 | 10 | impl Arbitrary for TextualRow { 11 | type Parameters = (); 12 | 13 | type Strategy = BoxedStrategy; 14 | 15 | fn arbitrary_with(_: ()) -> Self::Strategy { 16 | ( 17 | any::(), 18 | any::(), 19 | (10_000_000u64..20_000_000u64), 20 | (0..100), 21 | "[a-zA-Z]{100,1000}", 22 | ) 23 | .prop_map(|(level, target, tm, rate, msg)| TextualRow { 24 | msg: format!( 25 | "{level}{target} {tm} {}", 26 | if rate > 50 { 27 | format!("{msg}{MATCH}{msg}") 28 | } else { 29 | msg 30 | } 31 | ), 32 | }) 33 | .boxed() 34 | } 35 | } 36 | 37 | #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, PartialOrd, Clone)] 38 | pub struct JSONRow { 39 | pub meta: Metadata, 40 | pub msg: String, 41 | } 42 | 43 | impl Arbitrary for JSONRow { 44 | type Parameters = (); 45 | 46 | type Strategy = BoxedStrategy; 47 | 48 | fn arbitrary_with(_: ()) -> Self::Strategy { 49 | (any::(), (0..100), "[a-zA-Z]{100,1000}") 50 | .prop_map(|(meta, rate, msg)| JSONRow { 51 | msg: if rate > 50 { 52 | format!("{msg}{MATCH}{msg}") 53 | } else { 54 | msg 55 | }, 56 | meta, 57 | }) 58 | .boxed() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /brec_macros/src/entities/derives/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::{DeriveInput, Path}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Derives { 8 | derives: Vec, 9 | } 10 | 11 | impl Derives { 12 | pub fn common(derives: Vec<&Self>) -> Result, E> { 13 | let mut common = derives 14 | .first() 15 | .map(|der| der.derives.clone()) 16 | .unwrap_or_default(); 17 | derives.iter().for_each(|v| { 18 | common.retain(|der| v.derives.contains(der)); 19 | }); 20 | common 21 | .into_iter() 22 | .map(|derive| { 23 | let derive = derive.trim(); 24 | syn::parse_str::(derive) 25 | .map(|path| quote!(#path)) 26 | .map_err(|_e| E::FailParseDerive(derive.to_owned())) 27 | }) 28 | .collect() 29 | } 30 | } 31 | 32 | impl From<&DeriveInput> for Derives { 33 | fn from(input: &DeriveInput) -> Self { 34 | let mut derives = Vec::new(); 35 | input.attrs.iter().for_each(|attr| { 36 | if attr.path().is_ident("derive") { 37 | let _ = attr.parse_nested_meta(|meta| { 38 | let path: &Path = &meta.path; 39 | derives.push( 40 | path.segments 41 | .iter() 42 | .map(|seg| seg.ident.to_string()) 43 | .collect::>() 44 | .join("::"), 45 | ); 46 | Ok(()) 47 | }); 48 | } 49 | }); 50 | Self { derives } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /brec/src/payload/defaults/vec_u8.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use payload::*; 3 | 4 | /// `Vec` is supported as a default payload type in `brec`. 5 | /// 6 | /// This payload represents raw binary data. It supports full encoding/decoding, 7 | /// CRC validation, signature identification, and efficient vectored writing. 8 | /// No transformation or framing is applied — the raw byte content is stored and restored as-is. 9 | impl PayloadSize for Vec { 10 | fn size(&self) -> std::io::Result { 11 | Ok(self.len() as u64) 12 | } 13 | } 14 | 15 | impl PayloadCrc for Vec {} 16 | 17 | impl PayloadSignature for Vec { 18 | fn sig(&self) -> ByteBlock { 19 | as StaticPayloadSignature>::ssig() 20 | } 21 | } 22 | 23 | impl StaticPayloadSignature for Vec { 24 | fn ssig() -> ByteBlock { 25 | let mut hasher = crc32fast::Hasher::new(); 26 | hasher.update("Vec".as_bytes()); 27 | ByteBlock::Len4(hasher.finalize().to_le_bytes()) 28 | } 29 | } 30 | 31 | impl PayloadEncode for Vec { 32 | fn encode(&self) -> std::io::Result> { 33 | Ok(self.clone()) 34 | } 35 | } 36 | 37 | impl PayloadEncodeReferred for Vec { 38 | fn encode(&self) -> std::io::Result> { 39 | Ok(Some(self)) 40 | } 41 | } 42 | 43 | impl PayloadDecode> for Vec { 44 | fn decode(buf: &[u8]) -> std::io::Result> { 45 | Ok(buf.to_vec()) 46 | } 47 | } 48 | 49 | impl ReadPayloadFrom> for Vec {} 50 | 51 | impl TryReadPayloadFrom> for Vec {} 52 | 53 | impl TryReadPayloadFromBuffered> for Vec {} 54 | 55 | impl WritePayloadWithHeaderTo for Vec {} 56 | 57 | impl WriteVectoredPayloadWithHeaderTo for Vec {} 58 | 59 | impl PayloadHooks for Vec {} 60 | -------------------------------------------------------------------------------- /examples/wasmjs/src/blocks/block_enums.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | 3 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 4 | pub enum Level { 5 | Err, 6 | Warn, 7 | Info, 8 | Debug, 9 | } 10 | 11 | impl TryFrom for Level { 12 | type Error = String; 13 | fn try_from(value: u8) -> Result { 14 | match value { 15 | 0 => Ok(Level::Err), 16 | 1 => Ok(Level::Warn), 17 | 2 => Ok(Level::Debug), 18 | 3 => Ok(Level::Info), 19 | invalid => Err(format!("{invalid} isn't valid value for Level")), 20 | } 21 | } 22 | } 23 | 24 | impl From<&Level> for u8 { 25 | fn from(value: &Level) -> Self { 26 | match value { 27 | Level::Err => 0, 28 | Level::Warn => 1, 29 | Level::Debug => 2, 30 | Level::Info => 3, 31 | } 32 | } 33 | } 34 | 35 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 36 | pub enum Kind { 37 | File, 38 | Stream, 39 | Socket, 40 | } 41 | 42 | impl TryFrom for Kind { 43 | type Error = String; 44 | fn try_from(value: u8) -> Result { 45 | match value { 46 | 0 => Ok(Kind::File), 47 | 1 => Ok(Kind::Stream), 48 | 2 => Ok(Kind::Socket), 49 | invalid => Err(format!("{invalid} isn't valid value for Kind")), 50 | } 51 | } 52 | } 53 | 54 | impl From<&Kind> for u8 { 55 | fn from(value: &Kind) -> Self { 56 | match value { 57 | Kind::File => 0, 58 | Kind::Stream => 1, 59 | Kind::Socket => 2, 60 | } 61 | } 62 | } 63 | 64 | #[block] 65 | #[derive(serde::Deserialize, serde::Serialize)] 66 | pub struct BlockEnums { 67 | pub level: Level, 68 | pub kind: Kind, 69 | } 70 | -------------------------------------------------------------------------------- /brec/src/payload/defaults/string.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use payload::*; 3 | 4 | /// `String` is supported as a default payload type in `brec`. 5 | /// 6 | /// It provides complete support for encoding, decoding, signature verification, 7 | /// CRC validation, and vectored writing. The string is treated as UTF-8 encoded data 8 | /// and is serialized as-is without any additional framing or length prefix. 9 | impl PayloadSize for String { 10 | fn size(&self) -> std::io::Result { 11 | Ok(self.len() as u64) 12 | } 13 | } 14 | 15 | impl PayloadCrc for String {} 16 | 17 | impl PayloadSignature for String { 18 | fn sig(&self) -> ByteBlock { 19 | ::ssig() 20 | } 21 | } 22 | 23 | impl StaticPayloadSignature for String { 24 | fn ssig() -> ByteBlock { 25 | let mut hasher = crc32fast::Hasher::new(); 26 | hasher.update("String".as_bytes()); 27 | ByteBlock::Len4(hasher.finalize().to_le_bytes()) 28 | } 29 | } 30 | 31 | impl PayloadEncode for String { 32 | fn encode(&self) -> std::io::Result> { 33 | Ok(self.as_bytes().to_vec()) 34 | } 35 | } 36 | 37 | impl PayloadEncodeReferred for String { 38 | fn encode(&self) -> std::io::Result> { 39 | Ok(Some(self.as_bytes())) 40 | } 41 | } 42 | 43 | impl PayloadDecode for String { 44 | fn decode(buf: &[u8]) -> std::io::Result { 45 | Ok(String::from_utf8_lossy(buf).to_string()) 46 | } 47 | } 48 | 49 | impl ReadPayloadFrom for String {} 50 | 51 | impl TryReadPayloadFrom for String {} 52 | 53 | impl TryReadPayloadFromBuffered for String {} 54 | 55 | impl WritePayloadWithHeaderTo for String {} 56 | 57 | impl WriteVectoredPayloadWithHeaderTo for String {} 58 | 59 | impl PayloadHooks for String {} 60 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/type_def/ty/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | 5 | impl TypeDefinition for Ty { 6 | fn direct(&self) -> TokenStream { 7 | match self { 8 | Self::U8 9 | | Self::U16 10 | | Self::U32 11 | | Self::U64 12 | | Self::U128 13 | | Self::I8 14 | | Self::I16 15 | | Self::I32 16 | | Self::I64 17 | | Self::I128 18 | | Self::F32 19 | | Self::F64 20 | | Self::Bool => { 21 | let ty = format_ident!("{}", self.to_string()); 22 | quote! { #ty } 23 | } 24 | Self::Blob(len) => { 25 | quote! { [u8; #len] } 26 | } 27 | Self::LinkedToU8(ident) => { 28 | let ident = format_ident!("{ident}"); 29 | quote! { #ident } 30 | } 31 | } 32 | } 33 | fn referenced(&self) -> TokenStream { 34 | match self { 35 | Self::U8 36 | | Self::U16 37 | | Self::U32 38 | | Self::U64 39 | | Self::U128 40 | | Self::I8 41 | | Self::I16 42 | | Self::I32 43 | | Self::I64 44 | | Self::I128 45 | | Self::F32 46 | | Self::F64 47 | | Self::Bool => { 48 | let ty = format_ident!("{}", self.to_string()); 49 | quote! { &'a #ty } 50 | } 51 | Self::Blob(len) => { 52 | quote! { &'a [u8; #len] } 53 | } 54 | Self::LinkedToU8(ident) => { 55 | let ident = format_ident!("{ident}"); 56 | quote! { &'a #ident } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /brec/src/packet/referred.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::*; 4 | 5 | /// A lightweight representation of a parsed packet with block references and optional raw payload. 6 | /// 7 | /// This structure is used when: 8 | /// - You want to keep references to already-decoded blocks (`BR`) 9 | /// - You don’t need to immediately decode the payload (it can remain as a raw slice) 10 | /// 11 | /// Useful in zero-copy parsing scenarios or when working with external buffers. 12 | /// 13 | /// # Type Parameters 14 | /// - `B`: The original block type, implementing [`BlockDef`]. 15 | /// - `BR`: The referred/parsed block type, implementing [`BlockReferredDef`]. 16 | /// 17 | /// # Fields 18 | /// - `blocks`: A vector of referred block objects. 19 | /// - `header`: The parsed packet header. 20 | /// - `payload`: Optional raw payload slice (usually borrowed from a buffer). 21 | /// - `_b`: Phantom marker to retain the `B` type. 22 | pub struct PacketReferred<'a, B: BlockDef, BR: BlockReferredDef> { 23 | pub blocks: Vec
, 24 | pub header: PacketHeader, 25 | pub payload: Option<&'a [u8]>, 26 | _b: PhantomData, 27 | } 28 | 29 | impl> PacketReferred<'_, B, BR> { 30 | /// Constructs a new `PacketReferred` with the given blocks and header. 31 | /// 32 | /// The payload is not set by default and must be attached manually if needed. 33 | /// 34 | /// # Arguments 35 | /// - `blocks` – A list of parsed or referred blocks. 36 | /// - `header` – The parsed packet header associated with the packet. 37 | /// 38 | /// # Returns 39 | /// A new `PacketReferred` instance with `payload = None`. 40 | pub fn new(blocks: Vec
, header: PacketHeader) -> Self { 41 | Self { 42 | blocks, 43 | header, 44 | payload: None, 45 | _b: PhantomData, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /brec_macros/src/tokenized/block/read_exact/field/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | use syn::Ident; 4 | 5 | use crate::*; 6 | 7 | impl ReadExact for Field { 8 | fn read_exact(&self, src: &Ident) -> Result { 9 | let name = format_ident!("{}", self.name); 10 | let ty = self.ty.direct(); 11 | let len = self.ty.size(); 12 | match &self.ty { 13 | Ty::U8 => Ok(quote! { 14 | let mut #name = [0u8; 1]; 15 | #src.read_exact(&mut #name)?; 16 | let #name = #name[0]; 17 | }), 18 | Ty::U16 19 | | Ty::U32 20 | | Ty::U64 21 | | Ty::U128 22 | | Ty::I8 23 | | Ty::I16 24 | | Ty::I32 25 | | Ty::I64 26 | | Ty::I128 27 | | Ty::F32 28 | | Ty::F64 => Ok(quote! { 29 | let mut #name = [0u8; #len]; 30 | #src.read_exact(&mut #name)?; 31 | let #name = #ty::from_le_bytes(#name); 32 | }), 33 | Ty::Bool => Ok(quote! { 34 | let mut #name = [0u8; #len]; 35 | #src.read_exact(&mut #name)?; 36 | let #name = #name[0] != 0; 37 | }), 38 | Ty::Blob(len) => Ok(quote! { 39 | let mut #name = [0u8; #len]; 40 | #src.read_exact(&mut #name)?; 41 | }), 42 | Ty::LinkedToU8(enum_name) => { 43 | let ident = self.ty.direct(); 44 | Ok(quote! { 45 | let mut #name = [0u8; 1]; 46 | #src.read_exact(&mut #name)?; 47 | let #name = #ident::try_from(#name[0]).map_err(|err| brec::Error::FailedConverting(#enum_name.to_owned(), err))?; 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /brec_macros/src/collector/blocks/props.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn gen(blocks: &[&Block]) -> Result { 7 | let mut variants = Vec::new(); 8 | for blk in blocks.iter() { 9 | let fullname = blk.fullname()?; 10 | let fullpath = blk.fullpath()?; 11 | variants.push(quote! {Block::#fullname(..) => #fullpath::ssize()}); 12 | } 13 | Ok(quote! { 14 | impl brec::Size for Block { 15 | fn size(&self) -> u64 { 16 | use brec::StaticSize; 17 | match self { 18 | #(#variants,)* 19 | } 20 | } 21 | } 22 | }) 23 | } 24 | 25 | pub fn gen_referred(blocks: &[&Block]) -> Result { 26 | let mut variants = Vec::new(); 27 | for blk in blocks.iter() { 28 | let fullpath = blk.fullpath()?; 29 | let fullname = blk.fullname()?; 30 | variants.push(quote! {BlockReferred::#fullname(..) => #fullpath::ssize()}); 31 | } 32 | Ok(quote! { 33 | impl brec::Size for BlockReferred<'_> { 34 | fn size(&self) -> u64 { 35 | use brec::StaticSize; 36 | match self { 37 | #(#variants,)* 38 | } 39 | } 40 | } 41 | }) 42 | } 43 | 44 | pub fn referred_into(blocks: &[&Block]) -> Result { 45 | let mut variants = Vec::new(); 46 | for blk in blocks.iter() { 47 | let fullname = blk.fullname()?; 48 | variants.push(quote! {BlockReferred::#fullname(blk) => Block::#fullname(blk.into())}); 49 | } 50 | Ok(quote! { 51 | impl std::convert::From> for Block { 52 | fn from(blk: BlockReferred<'_>) -> Self { 53 | match blk { 54 | #(#variants,)* 55 | } 56 | } 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /brec_macros/src/entities/block/attr/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | use std::fmt; 5 | use syn::Ident; 6 | 7 | #[derive(Debug, Clone, Default)] 8 | pub struct BlockAttrs(pub Vec); 9 | 10 | impl BlockAttrs { 11 | pub fn fullpath(&self, name: Ident) -> Result { 12 | let Some(BlockAttr::Path(path)) = self 13 | .0 14 | .iter() 15 | .find(|attr| matches!(attr, BlockAttr::Path(..))) 16 | else { 17 | return Ok(quote! {#name}); 18 | }; 19 | path.join(format_ident!("{name}")) 20 | } 21 | pub fn fullname(&self, name: Ident) -> Result { 22 | Ok(format_ident!( 23 | "{}", 24 | self.fullpath(name)? 25 | .to_string() 26 | .split("::") 27 | .map(|s| { 28 | let mut chars = s.trim().chars(); 29 | match chars.next() { 30 | Some(first) => first.to_uppercase().chain(chars).collect(), 31 | None => String::new(), 32 | } 33 | }) 34 | .collect::>() 35 | .join("") 36 | )) 37 | } 38 | pub fn is_no_crc(&self) -> bool { 39 | self.0.iter().any(|attr| matches!(attr, BlockAttr::NoCrc)) 40 | } 41 | } 42 | 43 | #[enum_ids::enum_ids(display_variant_snake)] 44 | #[derive(Debug, Clone)] 45 | pub enum BlockAttr { 46 | Path(ModulePath), 47 | NoCrc, 48 | } 49 | 50 | impl fmt::Display for BlockAttr { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | write!( 53 | f, 54 | "{}", 55 | match self { 56 | Self::Path(path) => format!("{}({path})", self.id()), 57 | Self::NoCrc => BlockAttrId::NoCrc.to_string(), 58 | } 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/block/mod.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | 3 | use crate::*; 4 | use std::convert::TryFrom; 5 | use syn::{Data, DeriveInput, Fields}; 6 | 7 | pub const BLOCK_ATTR: &str = "block"; 8 | 9 | impl TryFrom<(BlockAttrs, &mut DeriveInput)> for Block { 10 | type Error = syn::Error; 11 | fn try_from((attrs, input): (BlockAttrs, &mut DeriveInput)) -> Result { 12 | let name = &input.ident; 13 | if !input.generics.params.is_empty() { 14 | return Err(syn::Error::new_spanned( 15 | &input.generics, 16 | E::GenericTypesNotSupported, 17 | )); 18 | } 19 | input.attrs.retain(|attr| !attr.path().is_ident(BLOCK_ATTR)); 20 | let mut extracted = Vec::new(); 21 | let Data::Struct(data_struct) = &mut input.data else { 22 | return Err(syn::Error::new_spanned( 23 | &input, 24 | E::NotSupportedBy(BLOCK_ATTR.to_string()), 25 | )); 26 | }; 27 | let Fields::Named(fields) = &mut data_struct.fields else { 28 | return Err(syn::Error::new_spanned( 29 | &data_struct.fields, 30 | E::NotSupportedBy(BLOCK_ATTR.to_string()), 31 | )); 32 | }; 33 | for field in &mut fields.named { 34 | extracted.push(Field::try_from(field)?); 35 | } 36 | extracted.insert(0, Field::injected(FIELD_SIG, Ty::Blob(4))); 37 | extracted.push(Field::injected(FIELD_CRC, Ty::Blob(4))); 38 | let blk = Self::new( 39 | name.to_string(), 40 | extracted, 41 | attrs, 42 | (&*input).into(), 43 | Vis::from(&*input), 44 | ); 45 | Collector::get() 46 | .map_err(|err| syn::Error::new_spanned(&input, err))? 47 | .add_block(blk.clone()) 48 | .map_err(|err| syn::Error::new_spanned(&input, err))?; 49 | Ok(blk) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /brec_macros/src/entities/modpath/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use std::{convert::TryFrom, fmt}; 4 | use syn::{parse_str, Expr, ExprPath, Ident, Lit, LitStr, Path}; 5 | 6 | use crate::*; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct ModulePath { 10 | pub inner: String, 11 | } 12 | 13 | impl ModulePath { 14 | pub fn join(&self, ident: Ident) -> Result { 15 | if let Ok(path) = parse_str::(&self.inner) { 16 | Ok(quote! { #path::#ident }) 17 | } else if let Ok(lit) = parse_str::(&self.inner) { 18 | let path: TokenStream = match lit.value().parse() { 19 | Ok(tk) => tk, 20 | Err(_) => return Err(E::FailParseFullpath), 21 | }; 22 | Ok(quote! { #path::#ident }) 23 | } else { 24 | Err(E::FailParseFullpath) 25 | } 26 | } 27 | } 28 | 29 | impl TryFrom<&Expr> for ModulePath { 30 | type Error = syn::Error; 31 | 32 | fn try_from(expr: &Expr) -> Result { 33 | let tk_ref = expr.clone(); 34 | let path = match expr { 35 | Expr::Lit(lit) => { 36 | let Lit::Str(path) = &lit.lit else { 37 | return Err(syn::Error::new_spanned(tk_ref, E::UnsupportedAttr)); 38 | }; 39 | path.to_token_stream() 40 | } 41 | Expr::Path(path) => path.to_token_stream(), 42 | _not_supported => { 43 | return Err(syn::Error::new_spanned(tk_ref, E::UnsupportedAttr)); 44 | } 45 | }; 46 | Ok(Self { 47 | inner: path.to_string(), 48 | }) 49 | } 50 | } 51 | 52 | impl From<&ExprPath> for ModulePath { 53 | fn from(expr: &ExprPath) -> Self { 54 | Self { 55 | inner: expr.path.to_token_stream().to_string(), 56 | } 57 | } 58 | } 59 | 60 | impl fmt::Display for ModulePath { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | write!(f, "{}", self.inner) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/block/attr/mod.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use crate::*; 4 | use syn::{ 5 | parse::{self, Parse, ParseStream}, 6 | punctuated::Punctuated, 7 | Expr, Token, 8 | }; 9 | 10 | impl Parse for BlockAttrs { 11 | fn parse(input: ParseStream) -> parse::Result { 12 | let mut attrs: Vec = vec![]; 13 | for expr in Punctuated::::parse_terminated(input)? { 14 | match expr { 15 | Expr::Assign(assign) => { 16 | let Expr::Path(key) = assign.left.as_ref() else { 17 | return Err(syn::Error::new_spanned(assign.left, E::FailExtractIdent)); 18 | }; 19 | let key = key 20 | .path 21 | .get_ident() 22 | .ok_or(syn::Error::new_spanned( 23 | assign.left.clone(), 24 | E::FailExtractIdent, 25 | ))? 26 | .to_string(); 27 | if key == BlockAttrId::Path.to_string() { 28 | attrs.push(BlockAttr::Path(ModulePath::try_from(&*assign.right)?)); 29 | } else { 30 | return Err(syn::Error::new_spanned(assign, E::UnsupportedAttr)); 31 | } 32 | } 33 | Expr::Path(expr) => { 34 | if let Some(ident) = expr.path.clone().get_ident() { 35 | let as_str = ident.to_string(); 36 | if as_str == BlockAttrId::NoCrc.to_string() { 37 | attrs.push(BlockAttr::NoCrc) 38 | } 39 | } else { 40 | attrs.push(BlockAttr::Path(ModulePath::from(&expr))); 41 | } 42 | } 43 | unknown => { 44 | return Err(syn::Error::new_spanned(unknown, E::UnsupportedAttr)); 45 | } 46 | } 47 | } 48 | Ok(BlockAttrs(attrs)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /brec_macros/src/collector/payloads/write.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn writing_to(payloads: &[&Payload]) -> Result { 7 | let mut write = Vec::new(); 8 | let mut write_all = Vec::new(); 9 | for payload in payloads.iter() { 10 | let fullname = payload.fullname()?; 11 | write.push( 12 | quote! {Payload::#fullname(pl) => brec::WritePayloadWithHeaderTo::write(pl, buf)}, 13 | ); 14 | write_all.push( 15 | quote! {Payload::#fullname(pl) => brec::WritePayloadWithHeaderTo::write_all(pl, buf)}, 16 | ); 17 | } 18 | Ok(quote! { 19 | impl brec::WriteMutTo for Payload { 20 | fn write(&mut self, buf: &mut T) -> std::io::Result { 21 | match self { 22 | #(#write,)* 23 | Payload::Bytes(pl) => brec::WritePayloadWithHeaderTo::write(pl, buf), 24 | Payload::String(pl) => brec::WritePayloadWithHeaderTo::write(pl, buf), 25 | } 26 | } 27 | 28 | fn write_all(&mut self, buf: &mut T) -> std::io::Result<()> { 29 | match self { 30 | #(#write_all,)* 31 | Payload::Bytes(pl) => brec::WritePayloadWithHeaderTo::write_all(pl, buf), 32 | Payload::String(pl) => brec::WritePayloadWithHeaderTo::write_all(pl, buf), 33 | } 34 | } 35 | } 36 | }) 37 | } 38 | 39 | pub fn writing_vectored_to(payloads: &[&Payload]) -> Result { 40 | let mut slices = Vec::new(); 41 | for payload in payloads.iter() { 42 | let fullname = payload.fullname()?; 43 | slices.push(quote! {Payload::#fullname(pl) => pl.slices()}); 44 | } 45 | Ok(quote! { 46 | impl brec::WriteVectoredMutTo for Payload { 47 | fn slices(&mut self) -> std::io::Result { 48 | use brec::WriteVectoredPayloadWithHeaderTo; 49 | match self { 50 | #(#slices,)* 51 | Payload::Bytes(pl) => pl.slices(), 52 | Payload::String(pl) => pl.slices(), 53 | } 54 | } 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /brec/src/packet/header/write.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Serializes a `PacketHeader` into the provided mutable buffer. 4 | /// 5 | /// This function writes the following fields in order: 6 | /// - Signature: `[u8; 8]` 7 | /// - Size: `u64` (little-endian) 8 | /// - Blocks length: `u64` (little-endian) 9 | /// - Payload flag: `u8` (`1` if payload exists, `0` otherwise) 10 | /// - CRC: `u32` (little-endian) 11 | /// 12 | /// # Arguments 13 | /// * `header` – Reference to the `PacketHeader` to serialize. 14 | /// * `buffer` – A mutable byte slice where the header will be written. 15 | /// Must be at least `PacketHeader::SIZE` bytes long. 16 | fn fill_buf(header: &PacketHeader, buffer: &mut [u8]) { 17 | let mut offset = 0; 18 | buffer[offset..offset + 8usize].copy_from_slice(&PACKET_SIG); 19 | offset += 8usize; 20 | buffer[offset..offset + 8usize].copy_from_slice(&header.size.to_le_bytes()); 21 | offset += 8usize; 22 | buffer[offset..offset + 8usize].copy_from_slice(&header.blocks_len.to_le_bytes()); 23 | offset += 8usize; 24 | buffer[offset..offset + 1usize].copy_from_slice(&[header.payload.into()]); 25 | offset += 1; 26 | buffer[offset..offset + 4usize].copy_from_slice(&header.crc.to_le_bytes()); 27 | } 28 | 29 | impl WriteTo for PacketHeader { 30 | /// Serializes and writes the `PacketHeader` into the given output stream. 31 | /// 32 | /// # Arguments 33 | /// * `buf` – A writer implementing `std::io::Write`. 34 | /// 35 | /// # Returns 36 | /// The number of bytes written (always `PacketHeader::SIZE`) on success. 37 | /// 38 | /// # Errors 39 | /// Returns an `std::io::Error` if writing fails. 40 | fn write(&self, buf: &mut T) -> std::io::Result { 41 | let mut buffer = [0u8; PacketHeader::SIZE as usize]; 42 | fill_buf(self, &mut buffer); 43 | buf.write(&buffer) 44 | } 45 | 46 | /// Writes the entire serialized `PacketHeader`, ensuring the full write completes. 47 | /// 48 | /// # Errors 49 | /// Returns an `std::io::Error` if writing fails. 50 | fn write_all(&self, buf: &mut T) -> std::io::Result<()> { 51 | let mut buffer = [0u8; PacketHeader::SIZE as usize]; 52 | fill_buf(self, &mut buffer); 53 | buf.write_all(&buffer) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/measurements/src/tests/text.rs: -------------------------------------------------------------------------------- 1 | use crate::test::MATCH; 2 | use crate::*; 3 | use std::io::Write; 4 | use std::{ 5 | fs::{metadata, File}, 6 | io::{BufRead, BufReader}, 7 | time::Instant, 8 | }; 9 | 10 | pub fn create_file(rows: Vec, mut count: usize, filename: &str) -> std::io::Result<()> { 11 | let tmp = std::env::temp_dir().join(filename); 12 | if tmp.exists() { 13 | return Ok(()); 14 | } 15 | let mut file = std::fs::OpenOptions::new() 16 | .read(true) 17 | .write(true) 18 | .create(true) 19 | .truncate(true) 20 | .open(&tmp)?; 21 | while count > 0 { 22 | for row in rows.iter() { 23 | file.write_all(format!("{}\n", row.msg,).as_bytes())?; 24 | } 25 | count -= 1; 26 | } 27 | file.flush() 28 | } 29 | 30 | pub fn read_file(filename: &str) -> std::io::Result<()> { 31 | let now = Instant::now(); 32 | let tmp = std::env::temp_dir().join(filename); 33 | let size = metadata(&tmp).expect("Read File Meta").len(); 34 | let file = File::open(tmp)?; 35 | let reader = BufReader::new(file); 36 | let mut count = 0; 37 | for line_result in reader.lines() { 38 | let _line = line_result?; 39 | count += 1; 40 | } 41 | report::add( 42 | report::Platform::Text, 43 | report::TestCase::Reading, 44 | report::TestResults { 45 | size, 46 | count, 47 | time: now.elapsed().as_millis(), 48 | }, 49 | ); 50 | Ok(()) 51 | } 52 | 53 | pub fn filter_file(filename: &str) -> std::io::Result<()> { 54 | let now = Instant::now(); 55 | let tmp = std::env::temp_dir().join(filename); 56 | let size = metadata(&tmp).expect("Read File Meta").len(); 57 | let file = File::open(tmp)?; 58 | let reader = BufReader::new(file); 59 | let mut count = 0; 60 | let err = Level::Err.to_string(); 61 | for line_result in reader.lines() { 62 | let line = line_result?; 63 | if line.contains(&err) && line.contains(MATCH) { 64 | count += 1; 65 | } 66 | } 67 | report::add( 68 | report::Platform::Text, 69 | report::TestCase::Filtering, 70 | report::TestResults { 71 | size, 72 | count, 73 | time: now.elapsed().as_millis(), 74 | }, 75 | ); 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/ty/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::convert::{TryFrom, TryInto}; 3 | use syn::{Expr, Ident, Type, TypeArray, TypePath}; 4 | 5 | impl TryFrom<&Type> for Ty { 6 | type Error = syn::Error; 7 | 8 | fn try_from(ty: &Type) -> Result { 9 | match ty { 10 | Type::Path(ty) => ty.try_into(), 11 | Type::Array(ty) => ty.try_into(), 12 | Type::Reference(ty) => Err(syn::Error::new_spanned(ty, E::ReferenceUnsupported)), 13 | _ => Err(syn::Error::new_spanned(ty, E::UnsupportedType)), 14 | } 15 | } 16 | } 17 | 18 | impl TryFrom<&TypePath> for Ty { 19 | type Error = syn::Error; 20 | fn try_from(ty: &TypePath) -> Result { 21 | if let Some(ident) = ty.path.get_ident() { 22 | ident.try_into() 23 | } else { 24 | Err(syn::Error::new_spanned(ty, E::UnsupportedType)) 25 | } 26 | } 27 | } 28 | 29 | impl TryFrom<&Ident> for Ty { 30 | type Error = syn::Error; 31 | fn try_from(ident: &Ident) -> Result { 32 | Ok(match ident.to_string().as_str() { 33 | "u8" => Ty::U8, 34 | "u16" => Ty::U16, 35 | "u32" => Ty::U32, 36 | "u64" => Ty::U64, 37 | "u128" => Ty::U128, 38 | "i8" => Ty::I8, 39 | "i16" => Ty::I16, 40 | "i32" => Ty::I32, 41 | "i64" => Ty::I64, 42 | "i128" => Ty::I128, 43 | "f32" => Ty::F32, 44 | "f64" => Ty::F64, 45 | "bool" => Ty::Bool, 46 | _linked => Ty::LinkedToU8(ident.to_string()), 47 | }) 48 | } 49 | } 50 | 51 | impl TryFrom<&TypeArray> for Ty { 52 | type Error = syn::Error; 53 | fn try_from(ty: &TypeArray) -> Result { 54 | fn extract_array_len(len: &Expr) -> Result { 55 | if let Expr::Lit(expr_lit) = len { 56 | if let syn::Lit::Int(lit_int) = &expr_lit.lit { 57 | return lit_int.base10_parse::(); 58 | } 59 | } 60 | Err(syn::Error::new_spanned(len, E::MissedArraySize)) 61 | } 62 | if !matches!(Ty::try_from(&*ty.elem)?, Ty::U8) { 63 | Err(syn::Error::new_spanned(&*ty.elem, E::UnsupportedType)) 64 | } else { 65 | Ok(Ty::Blob(extract_array_len(&ty.len)?)) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /brec_macros/src/generate/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use syn::{ 3 | parse::{self, Parse, ParseStream}, 4 | punctuated::Punctuated, 5 | Expr, Token, 6 | }; 7 | 8 | impl Parse for Config { 9 | fn parse(input: ParseStream) -> parse::Result { 10 | let mut settings: Vec = vec![]; 11 | for expr in Punctuated::::parse_terminated(input)? { 12 | match expr { 13 | Expr::Assign(assign) => { 14 | let Expr::Path(key) = assign.left.as_ref() else { 15 | return Err(syn::Error::new_spanned(assign.left, E::FailExtractIdent)); 16 | }; 17 | let key = key 18 | .path 19 | .get_ident() 20 | .ok_or(syn::Error::new_spanned( 21 | assign.left.clone(), 22 | E::FailExtractIdent, 23 | ))? 24 | .to_string(); 25 | if key == SettingId::PayloadsDerive.to_string() { 26 | if let Expr::Lit(expr_lit) = &*assign.right { 27 | if let syn::Lit::Str(lit_str) = &expr_lit.lit { 28 | settings.push(Setting::PayloadsDerive(lit_str.value())); 29 | continue; 30 | } 31 | } 32 | return Err(syn::Error::new_spanned(assign, E::UnsupportedAttr)); 33 | } else { 34 | return Err(syn::Error::new_spanned(assign, E::UnsupportedAttr)); 35 | } 36 | } 37 | Expr::Path(expr) => { 38 | if let Some(ident) = expr.path.clone().get_ident() { 39 | let as_str = ident.to_string(); 40 | if as_str == SettingId::NoDefaultPayload.to_string() { 41 | settings.push(Setting::NoDefaultPayload); 42 | continue; 43 | } 44 | } 45 | return Err(syn::Error::new_spanned(expr, E::UnsupportedAttr)); 46 | } 47 | unknown => { 48 | return Err(syn::Error::new_spanned(unknown, E::UnsupportedAttr)); 49 | } 50 | } 51 | } 52 | Ok(Config(settings)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/measurements/src/tests/json.rs: -------------------------------------------------------------------------------- 1 | use crate::test::MATCH; 2 | use crate::*; 3 | use std::io::Write; 4 | use std::{ 5 | fs::{metadata, File}, 6 | io::{BufRead, BufReader}, 7 | time::Instant, 8 | }; 9 | 10 | pub fn create_file(rows: Vec, mut count: usize, filename: &str) -> std::io::Result<()> { 11 | let tmp = std::env::temp_dir().join(filename); 12 | if tmp.exists() { 13 | return Ok(()); 14 | } 15 | let mut file = std::fs::OpenOptions::new() 16 | .read(true) 17 | .write(true) 18 | .create(true) 19 | .truncate(true) 20 | .open(&tmp)?; 21 | while count > 0 { 22 | for row in rows.iter() { 23 | file.write_all(format!("{}\n", serde_json::to_string(row)?).as_bytes())?; 24 | } 25 | count -= 1; 26 | } 27 | file.flush() 28 | } 29 | 30 | pub fn read_file(filename: &str) -> std::io::Result<()> { 31 | let now = Instant::now(); 32 | let tmp = std::env::temp_dir().join(filename); 33 | let size = metadata(&tmp).expect("Read File Meta").len(); 34 | let file = File::open(tmp)?; 35 | let reader = BufReader::new(file); 36 | let mut count = 0; 37 | for line_result in reader.lines() { 38 | let line = line_result?; 39 | let _ = serde_json::from_str::(&line)?; 40 | count += 1; 41 | } 42 | report::add( 43 | report::Platform::Json, 44 | report::TestCase::Reading, 45 | report::TestResults { 46 | size, 47 | count, 48 | time: now.elapsed().as_millis(), 49 | }, 50 | ); 51 | Ok(()) 52 | } 53 | 54 | pub fn filter_file(filename: &str) -> std::io::Result<()> { 55 | let now = Instant::now(); 56 | let tmp = std::env::temp_dir().join(filename); 57 | let size = metadata(&tmp).expect("Read File Meta").len(); 58 | let file = File::open(tmp)?; 59 | let reader = BufReader::new(file); 60 | let mut count = 0; 61 | for line_result in reader.lines() { 62 | let line = line_result?; 63 | let msg = serde_json::from_str::(&line)?; 64 | if matches!(msg.meta.level, Level::Err) && msg.msg.contains(MATCH) { 65 | count += 1; 66 | } 67 | } 68 | report::add( 69 | report::Platform::Json, 70 | report::TestCase::Filtering, 71 | report::TestResults { 72 | size, 73 | count, 74 | time: now.elapsed().as_millis(), 75 | }, 76 | ); 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/props/block.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | impl Crc for Block { 6 | fn gen(&self) -> Result { 7 | let block_name = self.name(); 8 | let referred_name = self.referred_name(); 9 | if self.attrs.is_no_crc() { 10 | return Ok(quote! { 11 | 12 | impl brec::CrcU32 for #block_name { 13 | 14 | fn crc(&self) -> [u8; 4] { 15 | [0u8, 0u8, 0u8, 0u8, ] 16 | } 17 | 18 | } 19 | 20 | impl brec::CrcU32 for #referred_name<'_> { 21 | 22 | fn crc(&self) -> [u8; 4] { 23 | [0u8, 0u8, 0u8, 0u8, ] 24 | } 25 | 26 | } 27 | 28 | }); 29 | } 30 | let mut hash_packet = Vec::new(); 31 | let mut hash_referred = Vec::new(); 32 | for field in self.fields.iter().filter(|f| !f.injected) { 33 | let value = field.to_bytes(true)?; 34 | let referred = field.to_bytes(false)?; 35 | hash_packet.push(quote! { 36 | hasher.update(#value); 37 | }); 38 | hash_referred.push(quote! { 39 | hasher.update(#referred); 40 | }); 41 | } 42 | Ok(quote! { 43 | 44 | impl brec::CrcU32 for #block_name { 45 | 46 | fn crc(&self) -> [u8; 4] { 47 | let mut hasher = brec::crc32fast::Hasher::new(); 48 | #(#hash_packet)* 49 | hasher.finalize().to_le_bytes() 50 | } 51 | 52 | } 53 | 54 | impl brec::CrcU32 for #referred_name<'_> { 55 | 56 | fn crc(&self) -> [u8; 4] { 57 | let mut hasher = brec::crc32fast::Hasher::new(); 58 | #(#hash_referred)* 59 | hasher.finalize().to_le_bytes() 60 | } 61 | 62 | } 63 | 64 | }) 65 | } 66 | } 67 | 68 | impl Size for Block { 69 | fn gen(&self) -> TokenStream { 70 | let block_name = self.name(); 71 | let mut size = 0u64; 72 | for field in self.fields.iter() { 73 | size += field.size() as u64; 74 | } 75 | quote! { 76 | 77 | impl brec::StaticSize for #block_name { 78 | 79 | fn ssize() -> u64 { 80 | #size 81 | } 82 | 83 | } 84 | 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /brec_macros/src/entities/block/mod.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | 3 | pub(crate) use attr::*; 4 | 5 | use crate::*; 6 | use crc32fast::Hasher; 7 | use proc_macro2::TokenStream; 8 | use quote::{format_ident, quote}; 9 | use syn::{Ident, LitInt}; 10 | 11 | pub(crate) const BLOCK_SIG_LEN: usize = 4; 12 | pub(crate) const BLOCK_CRC_LEN: usize = 4; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct Block { 16 | pub name: String, 17 | pub fields: Vec, 18 | pub attrs: BlockAttrs, 19 | pub derives: Derives, 20 | pub vis: Vis, 21 | } 22 | 23 | impl Block { 24 | pub fn new( 25 | name: String, 26 | fields: Vec, 27 | attrs: BlockAttrs, 28 | derives: Derives, 29 | vis: Vis, 30 | ) -> Self { 31 | Self { 32 | name, 33 | fields, 34 | attrs, 35 | derives, 36 | vis, 37 | } 38 | } 39 | pub fn sig(&self) -> TokenStream { 40 | // TODO: might be a conflict if do not consider a path 41 | let mut hasher = Hasher::new(); 42 | let snap = format!( 43 | "{};{}", 44 | self.name, 45 | self.fields 46 | .iter() 47 | .map(|f| format!("{}:{}", f.name, f.ty)) 48 | .collect::>() 49 | .join(";") 50 | ); 51 | hasher.update(snap.as_bytes()); 52 | let sig = hasher.finalize().to_le_bytes(); 53 | quote! { [#(#sig),*] } 54 | } 55 | pub fn sig_len(&self) -> TokenStream { 56 | let len_lit = LitInt::new(&BLOCK_SIG_LEN.to_string(), proc_macro2::Span::call_site()); 57 | quote! { #len_lit } 58 | } 59 | pub fn size(&self) -> usize { 60 | self.fields 61 | .iter() 62 | .map(|f| f.size()) 63 | .collect::>() 64 | .iter() 65 | .sum::() 66 | } 67 | pub fn const_sig_name(&self) -> Ident { 68 | format_ident!("{}", self.name.to_ascii_uppercase()) 69 | } 70 | pub fn name(&self) -> Ident { 71 | format_ident!("{}", self.name) 72 | } 73 | pub fn referred_name(&self) -> Ident { 74 | format_ident!("{}Referred", self.name()) 75 | } 76 | pub fn fullname(&self) -> Result { 77 | self.attrs.fullname(self.name()) 78 | } 79 | pub fn fullpath(&self) -> Result { 80 | self.attrs.fullpath(self.name()) 81 | } 82 | pub fn vis_token(&self) -> Result { 83 | self.vis.as_token() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /brec_macros/src/tests/struct.rs: -------------------------------------------------------------------------------- 1 | use crate::tests::*; 2 | use proptest::prelude::*; 3 | use quote::{format_ident, quote}; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct Struct { 7 | pub name: String, 8 | pub fields: Vec, 9 | } 10 | 11 | impl Struct { 12 | pub fn includes_not_ordered_ty(&self) -> bool { 13 | self.fields.iter().any(|f| !f.is_ordered_ty()) 14 | } 15 | } 16 | 17 | impl Arbitrary for Struct { 18 | type Parameters = (Target, u8); 19 | 20 | type Strategy = BoxedStrategy; 21 | 22 | fn arbitrary_with((target, deep): (Target, u8)) -> Self::Strategy { 23 | prop::collection::vec(Field::arbitrary_with((target, deep + 1)), 1..20) 24 | .prop_map(move |fields| Struct { 25 | name: gen_name(true), 26 | fields, 27 | }) 28 | .boxed() 29 | } 30 | } 31 | 32 | impl Generate for Struct { 33 | type Options = Target; 34 | fn declaration(&self, target: Target) -> TokenStream { 35 | let name = format_ident!("{}", self.name); 36 | let fields = self 37 | .fields 38 | .iter() 39 | .map(|f| f.declaration(())) 40 | .collect::>(); 41 | let mc = match target { 42 | Target::Block => quote! { 43 | #[block] 44 | #[derive(Debug)] 45 | #[allow(non_snake_case, non_camel_case_types)] 46 | }, 47 | Target::Payload => { 48 | let payload_macro = if self.includes_not_ordered_ty() { 49 | quote! { 50 | #[payload(bincode, no_crc)] 51 | } 52 | } else { 53 | quote! { 54 | #[payload(bincode)] 55 | } 56 | }; 57 | quote! { 58 | #payload_macro 59 | #[derive(serde::Deserialize, serde::Serialize, Debug)] 60 | #[allow(non_snake_case, non_camel_case_types)] 61 | } 62 | } 63 | }; 64 | quote! { 65 | #mc 66 | pub struct #name { 67 | #(#fields,)* 68 | } 69 | } 70 | } 71 | fn instance(&self, _: Target) -> TokenStream { 72 | let name = format_ident!("{}", self.name); 73 | let fields = self 74 | .fields 75 | .iter() 76 | .map(|f| f.instance(())) 77 | .collect::>(); 78 | quote! { 79 | #name { 80 | #(#fields,)* 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /brec_macros/src/parsing/payload/attr/mod.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use crate::*; 4 | use syn::{ 5 | parse::{self, Parse, ParseStream}, 6 | punctuated::Punctuated, 7 | Expr, Token, 8 | }; 9 | 10 | impl Parse for PayloadAttrs { 11 | fn parse(input: ParseStream) -> parse::Result { 12 | let mut attrs: Vec = vec![]; 13 | for expr in Punctuated::::parse_terminated(input)? { 14 | match expr { 15 | Expr::Assign(assign) => { 16 | let Expr::Path(key) = assign.left.as_ref() else { 17 | return Err(syn::Error::new_spanned(assign.left, E::FailExtractIdent)); 18 | }; 19 | let key = key 20 | .path 21 | .get_ident() 22 | .ok_or(syn::Error::new_spanned( 23 | assign.left.clone(), 24 | E::FailExtractIdent, 25 | ))? 26 | .to_string(); 27 | if key == PayloadAttrId::Path.to_string() { 28 | attrs.push(PayloadAttr::Path(ModulePath::try_from(&*assign.right)?)); 29 | } else { 30 | return Err(syn::Error::new_spanned(assign, E::UnsupportedAttr)); 31 | } 32 | } 33 | Expr::Path(expr) => { 34 | if let Some(ident) = expr.path.clone().get_ident() { 35 | let as_str = ident.to_string(); 36 | if as_str == PayloadAttrId::NoDefaultSig.to_string() { 37 | attrs.push(PayloadAttr::NoDefaultSig) 38 | } else if as_str == PayloadAttrId::Bincode.to_string() { 39 | attrs.push(PayloadAttr::Bincode) 40 | } else if as_str == PayloadAttrId::Hooks.to_string() { 41 | attrs.push(PayloadAttr::Hooks) 42 | } else if as_str == PayloadAttrId::NoAutoCrc.to_string() { 43 | attrs.push(PayloadAttr::NoAutoCrc) 44 | } else if as_str == PayloadAttrId::NoCrc.to_string() { 45 | attrs.push(PayloadAttr::NoCrc) 46 | } 47 | } else { 48 | attrs.push(PayloadAttr::Path(ModulePath::from(&expr))); 49 | } 50 | } 51 | unknown => { 52 | return Err(syn::Error::new_spanned(unknown, E::UnsupportedAttr)); 53 | } 54 | } 55 | } 56 | Ok(PayloadAttrs(attrs)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /brec/src/storage/locator.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// A helper structure for locating and reserving free space in a sequence of `Slot` entries. 4 | /// 5 | /// `FreeSlotLocator` is used to find the next available offset for writing data 6 | /// and to manage the process of inserting data into a slot-based structure (e.g. file or buffer). 7 | /// 8 | /// The locator keeps track of: 9 | /// - the index of the current slot (`next`) 10 | /// - the byte offset to the start of that slot (`slot_offset`) 11 | /// 12 | /// This allows efficiently iterating through `Slot`s while preserving position state. 13 | #[derive(Default)] 14 | pub struct FreeSlotLocator { 15 | next: usize, 16 | slot_offset: u64, 17 | } 18 | 19 | impl FreeSlotLocator { 20 | /// Returns the next available free offset across the slots. 21 | /// 22 | /// Internally advances to the next slot if the current one is full. 23 | /// 24 | /// # Arguments 25 | /// * `slots` – A slice of `Slot` structures to scan. 26 | /// 27 | /// # Returns 28 | /// The absolute offset in the full space, or `None` if no free slot was found. 29 | pub fn next(&mut self, slots: &[Slot]) -> Option { 30 | let slot = slots.get(self.next)?; 31 | let offset = match slot.get_free_slot_offset() { 32 | Some(offset) => offset, 33 | None => { 34 | self.slot_offset += slot.size() + slot.width(); 35 | self.next += 1; 36 | let slot = slots.get(self.next)?; 37 | slot.get_free_slot_offset()? 38 | } 39 | }; 40 | Some(self.slot_offset + offset) 41 | } 42 | 43 | /// Attempts to insert data of the given `length` into the current slot. 44 | /// 45 | /// # Arguments 46 | /// * `slots` – A mutable slice of `Slot` values. 47 | /// * `length` – The number of bytes to insert. 48 | /// 49 | /// # Errors 50 | /// Returns `Error::CannotInsertIntoSlot` if the current slot is not available or full. 51 | pub fn insert(&mut self, slots: &mut [Slot], length: u64) -> Result<(), Error> { 52 | let slot = slots 53 | .get_mut(self.next) 54 | .ok_or(Error::CannotInsertIntoSlot)?; 55 | slot.insert(length) 56 | } 57 | 58 | /// Returns the current slot index and its starting absolute offset. 59 | pub fn current(&self) -> (usize, u64) { 60 | (self.next, self.slot_offset) 61 | } 62 | 63 | pub fn setup(&mut self, slots: &[Slot]) { 64 | for slot in slots.iter() { 65 | if slot.get_free_slot_offset().is_some() { 66 | break; 67 | } 68 | self.slot_offset += slot.size() + slot.width(); 69 | self.next += 1; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /brec/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error("Not enought data; required = {0}")] 6 | NotEnoughData(usize), 7 | #[error("Not enought data to read signature; data len = {0}; required = {1}")] 8 | NotEnoughtSignatureData(usize, usize), 9 | #[error("Invalid data align; data len = {0}; required = {1}; offset = {2} (expected 0)")] 10 | InvalidAlign(usize, usize, usize), 11 | #[error("Invalid buffer capacity: {0}; expected: {1}")] 12 | InvalidCapacity(usize, String), 13 | #[error("TryFromSliceError: {0}")] 14 | TryFromSliceError(#[from] std::array::TryFromSliceError), 15 | #[error("Signature doesn't match to target entity")] 16 | SignatureDismatch, 17 | #[error("Crc doesn't match to target entity")] 18 | CrcDismatch, 19 | #[error("Same rule has been added already")] 20 | RuleDuplicate, 21 | #[error("Block has zero length")] 22 | ZeroLengthBlock, 23 | #[error("Attempt to read more blocks than allowed")] 24 | MaxBlocksCount, 25 | #[error("Misaligned slice pointer")] 26 | MisalignedPointer, 27 | #[error("Unexpected slice length")] 28 | UnexpectedSliceLength, 29 | #[error("Fail converting \"{0}\" with error: {1}")] 30 | FailedConverting(String, String), 31 | #[error("IO Error: {0}")] 32 | Io(#[from] std::io::Error), 33 | #[error("Fail to exctract data from vector for ByteBlock")] 34 | FailExtractByteBlock, 35 | #[error("Fail to read payload header")] 36 | FailToReadPayloadHeader, 37 | #[error("Memory allocation failed")] 38 | MemoryAllocationFailed, 39 | #[error("Encoding error: {0}")] 40 | EncodeError(String), 41 | #[error("No pending packet to accept")] 42 | NoPendingPacket, 43 | #[error("Fail to read packet header")] 44 | FailToReadPacketHeader, 45 | #[error("PacketBufReader fall down into invalid logic")] 46 | InvalidPacketReaderLogic, 47 | #[error("Fail to find free slot")] 48 | CannotFindFreeSlot, 49 | #[error("Fail to find free palce in slot")] 50 | CannotFindFreePlaceInSlot, 51 | #[error("Fail to insert data into slot")] 52 | CannotInsertIntoSlot, 53 | #[error("Damaged slot: {0}")] 54 | DamagedSlot(Box), 55 | #[error("Too many attempts to read block; made {0} attempts")] 56 | TooManyAttemptsToReadBlock(usize), 57 | #[error("Out of bounds; len = {0}; requested = {1}")] 58 | OutOfBounds(usize, usize), 59 | #[error("Path isn't a file: {0}")] 60 | PathIsNotFile(String), 61 | #[error("File is locked: {0}")] 62 | FileIsLocked(String), 63 | #[error("Timeout error. File is locked: {0}")] 64 | TimeoutToWaitLockedFile(String), 65 | #[error("Fail to lock file: {0}")] 66 | FailToLockFile(std::io::Error), 67 | #[error("Test error has been fired")] 68 | Test, 69 | } 70 | -------------------------------------------------------------------------------- /brec/src/payload/mod.rs: -------------------------------------------------------------------------------- 1 | mod defaults; 2 | mod header; 3 | 4 | pub use header::*; 5 | 6 | /// Optional lifecycle hooks for payload encoding and decoding. 7 | /// 8 | /// These hooks can be used to prepare the payload before serialization 9 | /// or to perform post-processing after deserialization. 10 | /// 11 | /// They are **never required** to do anything — by default, they are no-ops. 12 | /// 13 | /// Implement this trait when you want to: 14 | /// - Reset or update internal state before encoding 15 | /// - Validate or transform data after decoding 16 | pub trait PayloadHooks { 17 | /// Called before encoding begins. 18 | /// 19 | /// Can be used to perform cleanup, compute checksums, or update fields. 20 | fn before_encode(&mut self) -> std::io::Result<()> { 21 | Ok(()) 22 | } 23 | 24 | /// Called after decoding is complete. 25 | /// 26 | /// Can be used to validate, fix up or normalize decoded data. 27 | fn after_decode(&mut self) -> std::io::Result<()> { 28 | Ok(()) 29 | } 30 | } 31 | 32 | /// Trait for serializing a payload into a byte buffer. 33 | /// 34 | /// Requires `PayloadHooks`, so `before_encode()` will always be invoked before encoding. 35 | pub trait PayloadEncode: PayloadHooks { 36 | /// Encodes the payload and returns a `Vec` containing serialized bytes. 37 | /// 38 | /// # Returns 39 | /// The encoded byte buffer. 40 | /// 41 | /// # Errors 42 | /// Any I/O or serialization error encountered during encoding. 43 | fn encode(&self) -> std::io::Result>; 44 | } 45 | 46 | /// Provides an optional reference to an already-encoded payload. 47 | /// 48 | /// This is a performance optimization: if the payload was already serialized, 49 | /// this trait can return a reference to the existing bytes and skip re-encoding. 50 | /// 51 | /// Useful in zero-copy or deferred encoding scenarios. 52 | pub trait PayloadEncodeReferred { 53 | /// Optionally returns a reference to a pre-encoded payload. 54 | /// 55 | /// # Returns 56 | /// - `Some(&[u8])` if the encoded buffer is available. 57 | /// - `None` if the payload must be encoded with [`PayloadEncode`]. 58 | fn encode(&self) -> std::io::Result>; 59 | } 60 | 61 | /// Trait for decoding a payload from a byte buffer. 62 | /// 63 | /// Requires `PayloadHooks`, so `after_decode()` will always be called after decoding. 64 | pub trait PayloadDecode: PayloadHooks { 65 | /// Deserializes a payload from the provided byte slice. 66 | /// 67 | /// # Arguments 68 | /// * `buf` – The raw buffer containing the payload data. 69 | /// 70 | /// # Returns 71 | /// The decoded payload object. 72 | /// 73 | /// # Errors 74 | /// Any error encountered while decoding or validating the payload. 75 | fn decode(buf: &[u8]) -> std::io::Result; 76 | } 77 | -------------------------------------------------------------------------------- /brec_macros/src/tests/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::tests::*; 2 | use proc_macro2::TokenStream; 3 | use proptest::prelude::*; 4 | use quote::{format_ident, quote}; 5 | 6 | #[derive(Debug)] 7 | pub struct Packet { 8 | blocks: Vec, 9 | payload: Option, 10 | pub name: String, 11 | } 12 | 13 | impl Arbitrary for Packet { 14 | type Parameters = (); 15 | 16 | type Strategy = BoxedStrategy; 17 | 18 | fn arbitrary_with(_: ()) -> Self::Strategy { 19 | ( 20 | prop::collection::vec(Struct::arbitrary_with((Target::Block, 0)), 1..5), 21 | prop::option::of(Struct::arbitrary_with((Target::Payload, 0))), 22 | ) 23 | .prop_map(move |(blocks, payload)| Packet { 24 | name: gen_name(false), 25 | blocks, 26 | payload, 27 | }) 28 | .boxed() 29 | } 30 | } 31 | 32 | fn variant_name>(name: S) -> String { 33 | name.as_ref() 34 | .split("::") 35 | .map(|s| { 36 | let mut chars = s.trim().chars(); 37 | match chars.next() { 38 | Some(first) => first.to_uppercase().chain(chars).collect(), 39 | None => String::new(), 40 | } 41 | }) 42 | .collect::>() 43 | .join("") 44 | } 45 | impl Generate for Packet { 46 | type Options = (); 47 | fn declaration(&self, _: Self::Options) -> TokenStream { 48 | let blocks = self 49 | .blocks 50 | .iter() 51 | .map(|b| b.declaration(Target::Block)) 52 | .collect::>(); 53 | let payload = self 54 | .payload 55 | .as_ref() 56 | .map(|p| p.declaration(Target::Payload)) 57 | .unwrap_or_default(); 58 | quote! { 59 | #(#blocks)* 60 | #payload 61 | } 62 | } 63 | fn instance(&self, _: Self::Options) -> TokenStream { 64 | let blocks = self 65 | .blocks 66 | .iter() 67 | .map(|b| { 68 | let instance = b.instance(Target::Block); 69 | let name = format_ident!("{}", variant_name(&b.name)); 70 | quote! { 71 | Block::#name(#instance) 72 | } 73 | }) 74 | .collect::>(); 75 | let payload = self 76 | .payload 77 | .as_ref() 78 | .map(|p| { 79 | let payload = p.instance(Target::Payload); 80 | let name = format_ident!("{}", variant_name(&p.name)); 81 | quote! { Some( Payload::#name(#payload) ) } 82 | }) 83 | .unwrap_or(quote! { None }); 84 | quote! { 85 | Packet::new( 86 | vec![#(#blocks,)*], 87 | #payload 88 | ) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /brec_macros/src/entities/payload/attr/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | use std::fmt; 5 | use syn::Ident; 6 | 7 | #[derive(Debug, Clone, Default)] 8 | pub struct PayloadAttrs(pub Vec); 9 | 10 | impl PayloadAttrs { 11 | pub fn fullpath(&self, name: Ident) -> Result { 12 | let Some(PayloadAttr::Path(path)) = self 13 | .0 14 | .iter() 15 | .find(|attr| matches!(attr, PayloadAttr::Path(..))) 16 | else { 17 | return Ok(quote! {#name}); 18 | }; 19 | path.join(format_ident!("{name}")) 20 | } 21 | pub fn fullname(&self, name: Ident) -> Result { 22 | Ok(format_ident!( 23 | "{}", 24 | self.fullpath(name)? 25 | .to_string() 26 | .split("::") 27 | .map(|s| { 28 | let mut chars = s.trim().chars(); 29 | match chars.next() { 30 | Some(first) => first.to_uppercase().chain(chars).collect(), 31 | None => String::new(), 32 | } 33 | }) 34 | .collect::>() 35 | .join("") 36 | )) 37 | } 38 | pub fn no_default_sig(&self) -> bool { 39 | self.0 40 | .iter() 41 | .any(|attr| matches!(attr, PayloadAttr::NoDefaultSig)) 42 | } 43 | pub fn hooks(&self) -> bool { 44 | self.0.iter().any(|attr| matches!(attr, PayloadAttr::Hooks)) 45 | } 46 | pub fn is_no_auto_crc(&self) -> bool { 47 | self.0 48 | .iter() 49 | .any(|attr| matches!(attr, PayloadAttr::NoAutoCrc)) 50 | } 51 | pub fn is_no_crc(&self) -> bool { 52 | self.0.iter().any(|attr| matches!(attr, PayloadAttr::NoCrc)) 53 | } 54 | pub fn is_bincode(&self) -> bool { 55 | self.0 56 | .iter() 57 | .any(|attr| matches!(attr, PayloadAttr::Bincode)) 58 | } 59 | } 60 | #[enum_ids::enum_ids(display_variant_snake)] 61 | #[derive(Debug, Clone)] 62 | pub enum PayloadAttr { 63 | Path(ModulePath), 64 | NoDefaultSig, 65 | Hooks, 66 | NoAutoCrc, 67 | NoCrc, 68 | Bincode, 69 | } 70 | 71 | impl fmt::Display for PayloadAttr { 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 73 | write!( 74 | f, 75 | "{}", 76 | match self { 77 | Self::Path(path) => format!("{}({path})", self.id()), 78 | Self::NoDefaultSig => PayloadAttrId::NoDefaultSig.to_string(), 79 | Self::Hooks => PayloadAttrId::Hooks.to_string(), 80 | Self::NoAutoCrc => PayloadAttrId::NoAutoCrc.to_string(), 81 | Self::NoCrc => PayloadAttrId::NoCrc.to_string(), 82 | Self::Bincode => PayloadAttrId::Bincode.to_string(), 83 | } 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/stress_blocks/src/block_enums.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 5 | pub enum Level { 6 | Err, 7 | Warn, 8 | Info, 9 | Debug, 10 | } 11 | 12 | impl TryFrom for Level { 13 | type Error = String; 14 | fn try_from(value: u8) -> Result { 15 | match value { 16 | 0 => Ok(Level::Err), 17 | 1 => Ok(Level::Warn), 18 | 2 => Ok(Level::Debug), 19 | 3 => Ok(Level::Info), 20 | invalid => Err(format!("{invalid} isn't valid value for Level")), 21 | } 22 | } 23 | } 24 | 25 | impl From<&Level> for u8 { 26 | fn from(value: &Level) -> Self { 27 | match value { 28 | Level::Err => 0, 29 | Level::Warn => 1, 30 | Level::Debug => 2, 31 | Level::Info => 3, 32 | } 33 | } 34 | } 35 | 36 | impl Arbitrary for Level { 37 | type Parameters = (); 38 | 39 | type Strategy = BoxedStrategy; 40 | 41 | fn arbitrary_with(_: ()) -> Self::Strategy { 42 | prop_oneof![ 43 | Just(Level::Err), 44 | Just(Level::Warn), 45 | Just(Level::Debug), 46 | Just(Level::Info) 47 | ] 48 | .boxed() 49 | } 50 | } 51 | 52 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 53 | pub enum Kind { 54 | File, 55 | Stream, 56 | Socket, 57 | } 58 | 59 | impl TryFrom for Kind { 60 | type Error = String; 61 | fn try_from(value: u8) -> Result { 62 | match value { 63 | 0 => Ok(Kind::File), 64 | 1 => Ok(Kind::Stream), 65 | 2 => Ok(Kind::Socket), 66 | invalid => Err(format!("{invalid} isn't valid value for Kind")), 67 | } 68 | } 69 | } 70 | 71 | impl From<&Kind> for u8 { 72 | fn from(value: &Kind) -> Self { 73 | match value { 74 | Kind::File => 0, 75 | Kind::Stream => 1, 76 | Kind::Socket => 2, 77 | } 78 | } 79 | } 80 | 81 | impl Arbitrary for Kind { 82 | type Parameters = (); 83 | 84 | type Strategy = BoxedStrategy; 85 | 86 | fn arbitrary_with(_: ()) -> Self::Strategy { 87 | prop_oneof![Just(Kind::File), Just(Kind::Stream), Just(Kind::Socket)].boxed() 88 | } 89 | } 90 | 91 | #[block] 92 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 93 | pub struct BlockEnums { 94 | pub level: Level, 95 | pub kind: Kind, 96 | } 97 | 98 | impl Arbitrary for BlockEnums { 99 | type Parameters = (); 100 | 101 | type Strategy = BoxedStrategy; 102 | 103 | fn arbitrary_with(_: ()) -> Self::Strategy { 104 | (Level::arbitrary(), Kind::arbitrary()) 105 | .prop_map(|(level, kind)| BlockEnums { level, kind }) 106 | .boxed() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/block_enums.rs: -------------------------------------------------------------------------------- 1 | use brec::prelude::*; 2 | use proptest::prelude::*; 3 | 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 5 | pub enum Level { 6 | Err, 7 | Warn, 8 | Info, 9 | Debug, 10 | } 11 | 12 | impl TryFrom for Level { 13 | type Error = String; 14 | fn try_from(value: u8) -> Result { 15 | match value { 16 | 0 => Ok(Level::Err), 17 | 1 => Ok(Level::Warn), 18 | 2 => Ok(Level::Debug), 19 | 3 => Ok(Level::Info), 20 | invalid => Err(format!("{invalid} isn't valid value for Level")), 21 | } 22 | } 23 | } 24 | 25 | impl From<&Level> for u8 { 26 | fn from(value: &Level) -> Self { 27 | match value { 28 | Level::Err => 0, 29 | Level::Warn => 1, 30 | Level::Debug => 2, 31 | Level::Info => 3, 32 | } 33 | } 34 | } 35 | 36 | impl Arbitrary for Level { 37 | type Parameters = (); 38 | 39 | type Strategy = BoxedStrategy; 40 | 41 | fn arbitrary_with(_: ()) -> Self::Strategy { 42 | prop_oneof![ 43 | Just(Level::Err), 44 | Just(Level::Warn), 45 | Just(Level::Debug), 46 | Just(Level::Info) 47 | ] 48 | .boxed() 49 | } 50 | } 51 | 52 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 53 | pub enum Kind { 54 | File, 55 | Stream, 56 | Socket, 57 | } 58 | 59 | impl TryFrom for Kind { 60 | type Error = String; 61 | fn try_from(value: u8) -> Result { 62 | match value { 63 | 0 => Ok(Kind::File), 64 | 1 => Ok(Kind::Stream), 65 | 2 => Ok(Kind::Socket), 66 | invalid => Err(format!("{invalid} isn't valid value for Kind")), 67 | } 68 | } 69 | } 70 | 71 | impl From<&Kind> for u8 { 72 | fn from(value: &Kind) -> Self { 73 | match value { 74 | Kind::File => 0, 75 | Kind::Stream => 1, 76 | Kind::Socket => 2, 77 | } 78 | } 79 | } 80 | 81 | impl Arbitrary for Kind { 82 | type Parameters = (); 83 | 84 | type Strategy = BoxedStrategy; 85 | 86 | fn arbitrary_with(_: ()) -> Self::Strategy { 87 | prop_oneof![Just(Kind::File), Just(Kind::Stream), Just(Kind::Socket)].boxed() 88 | } 89 | } 90 | 91 | #[block] 92 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 93 | pub struct BlockEnums { 94 | pub level: Level, 95 | pub kind: Kind, 96 | } 97 | 98 | impl Arbitrary for BlockEnums { 99 | type Parameters = (); 100 | 101 | type Strategy = BoxedStrategy; 102 | 103 | fn arbitrary_with(_: ()) -> Self::Strategy { 104 | (Level::arbitrary(), Kind::arbitrary()) 105 | .prop_map(|(level, kind)| BlockEnums { level, kind }) 106 | .boxed() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/stress_packets/src/blocks/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod block_blob; 3 | #[cfg(test)] 4 | mod block_blobs_max; 5 | #[cfg(test)] 6 | mod block_bool; 7 | #[cfg(test)] 8 | mod block_comb; 9 | #[cfg(test)] 10 | mod block_enums; 11 | #[cfg(test)] 12 | mod block_f32; 13 | #[cfg(test)] 14 | mod block_f64; 15 | #[cfg(test)] 16 | mod block_i128; 17 | #[cfg(test)] 18 | mod block_i16; 19 | #[cfg(test)] 20 | mod block_i32; 21 | #[cfg(test)] 22 | mod block_i64; 23 | #[cfg(test)] 24 | mod block_i8; 25 | #[cfg(test)] 26 | mod block_u128; 27 | #[cfg(test)] 28 | mod block_u16; 29 | #[cfg(test)] 30 | mod block_u32; 31 | #[cfg(test)] 32 | mod block_u64; 33 | #[cfg(test)] 34 | mod block_u8; 35 | 36 | #[cfg(test)] 37 | pub(crate) use block_blob::*; 38 | #[cfg(test)] 39 | pub(crate) use block_blobs_max::*; 40 | #[cfg(test)] 41 | pub(crate) use block_bool::*; 42 | #[cfg(test)] 43 | pub(crate) use block_comb::*; 44 | #[cfg(test)] 45 | pub(crate) use block_enums::*; 46 | #[cfg(test)] 47 | pub(crate) use block_f32::*; 48 | #[cfg(test)] 49 | pub(crate) use block_f64::*; 50 | #[cfg(test)] 51 | pub(crate) use block_i128::*; 52 | #[cfg(test)] 53 | pub(crate) use block_i16::*; 54 | #[cfg(test)] 55 | pub(crate) use block_i32::*; 56 | #[cfg(test)] 57 | pub(crate) use block_i64::*; 58 | #[cfg(test)] 59 | pub(crate) use block_i8::*; 60 | #[cfg(test)] 61 | pub(crate) use block_u128::*; 62 | #[cfg(test)] 63 | pub(crate) use block_u16::*; 64 | #[cfg(test)] 65 | pub(crate) use block_u32::*; 66 | #[cfg(test)] 67 | pub(crate) use block_u64::*; 68 | #[cfg(test)] 69 | pub(crate) use block_u8::*; 70 | 71 | use crate::*; 72 | use proptest::prelude::*; 73 | 74 | impl Arbitrary for Block { 75 | type Parameters = (); 76 | 77 | type Strategy = BoxedStrategy; 78 | 79 | fn arbitrary_with(_: ()) -> Self::Strategy { 80 | prop_oneof![ 81 | BlockU8::arbitrary().prop_map(Block::BlockU8), 82 | BlockU16::arbitrary().prop_map(Block::BlockU16), 83 | BlockU32::arbitrary().prop_map(Block::BlockU32), 84 | BlockU64::arbitrary().prop_map(Block::BlockU64), 85 | BlockU128::arbitrary().prop_map(Block::BlockU128), 86 | BlockI8::arbitrary().prop_map(Block::BlockI8), 87 | BlockI16::arbitrary().prop_map(Block::BlockI16), 88 | BlockI32::arbitrary().prop_map(Block::BlockI32), 89 | BlockI64::arbitrary().prop_map(Block::BlockI64), 90 | BlockI128::arbitrary().prop_map(Block::BlockI128), 91 | BlockF32::arbitrary().prop_map(Block::BlockF32), 92 | BlockF64::arbitrary().prop_map(Block::BlockF64), 93 | BlockBool::arbitrary().prop_map(Block::BlockBool), 94 | BlockBlob::arbitrary().prop_map(Block::BlockBlob), 95 | BlockBlobs::arbitrary().prop_map(Block::BlockBlobs), 96 | BlockEnums::arbitrary().prop_map(Block::BlockEnums), 97 | BlockCombination::arbitrary().prop_map(Block::BlockCombination), 98 | ] 99 | .boxed() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/base/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | impl Base for Payload { 6 | fn gen(&self) -> Result { 7 | let payload_name = self.name(); 8 | let sig = self.sig()?; 9 | let sig_impl = if self.attrs.no_default_sig() { 10 | quote! {} 11 | } else { 12 | quote! { 13 | 14 | impl brec::PayloadSignature for #payload_name { 15 | fn sig(&self) -> brec::ByteBlock { 16 | brec::ByteBlock::Len4(#sig) 17 | } 18 | } 19 | impl brec::StaticPayloadSignature for #payload_name { 20 | fn ssig() -> brec::ByteBlock { 21 | brec::ByteBlock::Len4(#sig) 22 | } 23 | } 24 | 25 | } 26 | }; 27 | 28 | let hooks_impl = if self.attrs.hooks() { 29 | quote! {} 30 | } else { 31 | quote! { 32 | impl brec::PayloadHooks for #payload_name { } 33 | } 34 | }; 35 | Ok(if self.attrs.is_bincode() { 36 | quote! { 37 | #sig_impl 38 | 39 | #hooks_impl 40 | 41 | impl brec::PayloadEncode for #payload_name { 42 | fn encode(&self) -> std::io::Result> { 43 | brec::bincode::serialize(self) 44 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) 45 | } 46 | } 47 | 48 | impl brec::PayloadEncodeReferred for #payload_name { 49 | fn encode(&self) -> std::io::Result> { 50 | Ok(None) 51 | } 52 | } 53 | 54 | impl brec::PayloadDecode<#payload_name> for #payload_name { 55 | fn decode(buf: &[u8]) -> std::io::Result<#payload_name> { 56 | brec::bincode::deserialize(buf) 57 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) 58 | } 59 | } 60 | } 61 | } else { 62 | quote! { 63 | #sig_impl 64 | #hooks_impl 65 | 66 | } 67 | }) 68 | } 69 | } 70 | 71 | impl Gen for Payload { 72 | fn gen(&self) -> Result { 73 | let base = Base::gen(self)?; 74 | let read = Read::gen(self)?; 75 | let try_read = TryRead::gen(self)?; 76 | let try_read_buffered = TryReadBuffered::gen(self)?; 77 | let crc = Crc::gen(self)?; 78 | let size = Size::gen(self); 79 | let write = Write::gen(self)?; 80 | let write_vec = WriteVectored::gen(self)?; 81 | Ok(quote! { 82 | #base 83 | #crc 84 | #size 85 | #read 86 | #try_read 87 | #try_read_buffered 88 | #write 89 | #write_vec 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /brec_macros/src/entities/ty/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// f16 and f128 are unstable 4 | #[enum_ids::enum_ids(display_variant)] 5 | #[derive(Debug, PartialEq, Clone)] 6 | #[allow(dead_code)] 7 | pub enum Ty { 8 | U8, 9 | U16, 10 | U32, 11 | U64, 12 | U128, 13 | I8, 14 | I16, 15 | I32, 16 | I64, 17 | I128, 18 | F32, 19 | F64, 20 | Bool, 21 | Blob(usize), 22 | LinkedToU8(String), 23 | } 24 | 25 | impl Ty { 26 | pub fn size(&self) -> usize { 27 | match self { 28 | Self::U8 => std::mem::size_of::(), 29 | Self::U16 => std::mem::size_of::(), 30 | Self::U32 => std::mem::size_of::(), 31 | Self::U64 => std::mem::size_of::(), 32 | Self::U128 => std::mem::size_of::(), 33 | Self::I8 => std::mem::size_of::(), 34 | Self::I16 => std::mem::size_of::(), 35 | Self::I32 => std::mem::size_of::(), 36 | Self::I64 => std::mem::size_of::(), 37 | Self::I128 => std::mem::size_of::(), 38 | Self::F32 => std::mem::size_of::(), 39 | Self::F64 => std::mem::size_of::(), 40 | Self::Bool => std::mem::size_of::(), 41 | Self::Blob(len) => *len, 42 | Self::LinkedToU8(..) => std::mem::size_of::(), 43 | } 44 | } 45 | } 46 | 47 | impl fmt::Display for Ty { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | write!( 50 | f, 51 | "{}", 52 | match self { 53 | Self::U8 => TyId::U8.to_string().to_ascii_lowercase(), 54 | Self::U16 => TyId::U16.to_string().to_ascii_lowercase(), 55 | Self::U32 => TyId::U32.to_string().to_ascii_lowercase(), 56 | Self::U64 => TyId::U64.to_string().to_ascii_lowercase(), 57 | Self::U128 => TyId::U128.to_string().to_ascii_lowercase(), 58 | Self::I8 => TyId::I8.to_string().to_ascii_lowercase(), 59 | Self::I16 => TyId::I16.to_string().to_ascii_lowercase(), 60 | Self::I32 => TyId::I32.to_string().to_ascii_lowercase(), 61 | Self::I64 => TyId::I64.to_string().to_ascii_lowercase(), 62 | Self::I128 => TyId::I128.to_string().to_ascii_lowercase(), 63 | Self::F32 => TyId::F32.to_string().to_ascii_lowercase(), 64 | Self::F64 => TyId::F64.to_string().to_ascii_lowercase(), 65 | Self::Bool => TyId::Bool.to_string().to_ascii_lowercase(), 66 | Self::Blob(len) => { 67 | // Just to avoid rust warning "never constructed" for TyId::Blob 68 | let _ = TyId::Blob.to_string(); 69 | format!("[u8;{len}]") 70 | } 71 | Self::LinkedToU8(ident) => { 72 | // Just to avoid rust warning "never constructed" for TyId::LinkedToU8 73 | let _ = TyId::LinkedToU8.to_string(); 74 | ident.to_string() 75 | } 76 | } 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `brec` 2 | 3 | First of all, thank you for your interest in contributing to `brec`! 4 | We welcome contributions of all kinds — bug reports, performance improvements, documentation fixes, or new features. 5 | 6 | Please follow the guidelines below to ensure a smooth review and merge process. 7 | 8 | --- 9 | 10 | ## Workflow 11 | 12 | The contribution process follows the standard GitHub workflow: 13 | 14 | 1. **Fork** the repository. 15 | 2. **Create a new branch** for your feature or fix. 16 | 3. **Implement your changes.** 17 | 4. **Open a Pull Request**, describing: 18 | - what the changes do, 19 | - why they are necessary or useful. 20 | 21 | Please be clear and thorough in your description — it helps maintainers understand your intentions quickly. 22 | 23 | --- 24 | 25 | ## Performance Considerations (Mandatory) 26 | 27 | `brec` is designed with high performance and low overhead in mind. 28 | If your changes affect core logic (reading, writing, parsing, or streaming), you must ensure there is **no performance degradation**. 29 | 30 | To measure this, run the existing benchmark test before and after your changes: 31 | 32 | ```bash 33 | cd tests/measurements 34 | cargo test --release -- --nocapture 35 | ``` 36 | 37 | 1. Run this test **on the unmodified main branch**, and note the results. 38 | 2. Then run the same test **after your changes**. 39 | 40 | Include the performance comparison in your pull request description. 41 | Unjustified performance regressions may result in the PR being rejected — unless the change is required to fix a critical bug. 42 | 43 | --- 44 | 45 | ## Testing Requirements 46 | 47 | There are two levels of tests: 48 | 49 | ### 1. `test.sh` — Fast CI Tests 50 | 51 | A lightweight test script that runs quickly and is used in the CI pipeline. 52 | 53 | You can run it locally: 54 | 55 | ```bash 56 | ./test.sh 57 | ``` 58 | 59 | > This test is **not sufficient** for merging critical changes. 60 | 61 | --- 62 | 63 | ### 2. `stress.sh` — Mandatory for Critical Paths 64 | 65 | The full stress test script generates over **40 GB of test data** to validate robustness. 66 | 67 | Run it manually before submitting PRs that: 68 | - modify file I/O logic, 69 | - affect reading/writing of packets, 70 | - or change serialization/deserialization behavior. 71 | 72 | ```bash 73 | ./stress.sh 74 | ``` 75 | 76 | This is the **final gate** for acceptance of changes. 77 | 78 | --- 79 | 80 | ## Linting & Style 81 | 82 | Before submitting a pull request, run: 83 | 84 | ```bash 85 | ./lint.sh 86 | ``` 87 | 88 | This checks formatting, lints the code, and ensures consistency. 89 | 90 | --- 91 | 92 | ## Documentation Guidelines 93 | 94 | - All **public methods** must be documented with clear descriptions of parameters and return values. 95 | - **Private methods** may remain undocumented, although **documentation is encouraged** wherever it helps future maintainers. 96 | 97 | --- 98 | 99 | Thank you again for contributing — we appreciate your time and effort! 100 | If you have any questions, feel free to open an issue or discussion thread. 101 | -------------------------------------------------------------------------------- /tests/measurements/src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proptest::prelude::*; 3 | use serial_test::serial; 4 | 5 | pub const MATCH: &str = "-match-"; 6 | 7 | brec::generate!(payloads_derive = "Clone, Debug"); 8 | 9 | #[derive(Debug)] 10 | pub struct WrappedPacket { 11 | blocks: Vec, 12 | payload: Option, 13 | } 14 | 15 | impl From<&WrappedPacket> for Packet { 16 | fn from(wrapped: &WrappedPacket) -> Self { 17 | Packet::new(wrapped.blocks.clone(), wrapped.payload.clone()) 18 | } 19 | } 20 | 21 | impl From for WrappedPacket { 22 | fn from(pkg: Packet) -> Self { 23 | WrappedPacket { 24 | blocks: pkg.blocks, 25 | payload: pkg.payload, 26 | } 27 | } 28 | } 29 | 30 | impl Arbitrary for WrappedPacket { 31 | type Parameters = (); 32 | 33 | type Strategy = BoxedStrategy; 34 | 35 | fn arbitrary_with(_: ()) -> Self::Strategy { 36 | any::() 37 | .prop_map(|record| WrappedPacket { 38 | blocks: vec![Block::Metadata(record.mt)], 39 | payload: Some(Payload::String(record.msg)), 40 | }) 41 | .boxed() 42 | } 43 | } 44 | 45 | const TEXT_LOG_FILE: &str = "test_measurements.log"; 46 | const JSON_LOG_FILE: &str = "test_measurements.json"; 47 | const BIN_LOG_FILE: &str = "test_measurements.bin"; 48 | const BIN_STREAM_LOG_FILE: &str = "test_measurements_stream.bin"; 49 | 50 | proptest! { 51 | #![proptest_config(ProptestConfig { 52 | max_shrink_iters: 50, 53 | ..ProptestConfig::with_cases(10) 54 | })] 55 | 56 | 57 | #[test] 58 | #[serial] 59 | fn text_logs(rows in proptest::collection::vec(any::(), 100)) { 60 | tests::text::create_file(rows,10_000, TEXT_LOG_FILE)?; 61 | tests::text::read_file(TEXT_LOG_FILE)?; 62 | tests::text::filter_file(TEXT_LOG_FILE)?; 63 | } 64 | 65 | #[test] 66 | #[serial] 67 | fn json_logs(rows in proptest::collection::vec(any::(), 100)) { 68 | tests::json::create_file(rows,10_000, JSON_LOG_FILE)?; 69 | tests::json::read_file(JSON_LOG_FILE)?; 70 | tests::json::filter_file(JSON_LOG_FILE)?; 71 | } 72 | 73 | #[test] 74 | #[serial] 75 | fn bin_logs(rows in proptest::collection::vec(any::(), 100)) { 76 | tests::storage::create_file(rows, 10_000, BIN_LOG_FILE)?; 77 | tests::storage::read_file(BIN_LOG_FILE)?; 78 | tests::storage::filter_file(BIN_LOG_FILE)?; 79 | } 80 | 81 | #[test] 82 | #[serial] 83 | fn bin_logs_stream(rows in proptest::collection::vec(any::(), 100)) { 84 | tests::stream::create_file(rows, 10_000, BIN_STREAM_LOG_FILE)?; 85 | tests::stream::read_file(BIN_STREAM_LOG_FILE)?; 86 | tests::stream::filter_file(BIN_STREAM_LOG_FILE)?; 87 | } 88 | 89 | #[test] 90 | #[serial] 91 | fn bin_logs_storage_streamed(rows in proptest::collection::vec(any::(), 100)) { 92 | tests::streamed_storage::create_file(rows, 10_000, BIN_LOG_FILE)?; 93 | tests::streamed_storage::read_file(BIN_LOG_FILE)?; 94 | tests::streamed_storage::filter_file(BIN_LOG_FILE)?; 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /brec/src/storage/slot/header.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// Represents the header of a storage slot, containing metadata about the slot's capacity. 4 | /// 5 | /// This header is used to identify and describe a fixed-size memory region (or file region) 6 | /// that can be used for storing data chunks. It begins with a predefined signature (`STORAGE_SLOT_SIG`) 7 | /// followed by the capacity of the slot (in bytes). 8 | pub struct SlotHeader { 9 | /// Total capacity of the slot (excluding the header itself). 10 | pub capacity: u64, 11 | } 12 | 13 | impl StaticSize for SlotHeader { 14 | /// Returns the static size of the slot header in bytes. 15 | /// 16 | /// Includes: 17 | /// - 8 bytes for the signature 18 | /// - 8 bytes for the capacity field 19 | fn ssize() -> u64 { 20 | (STORAGE_SLOT_SIG.len() + std::mem::size_of::()) as u64 21 | } 22 | } 23 | 24 | impl ReadFrom for SlotHeader { 25 | /// Reads a `SlotHeader` from the provided stream. 26 | /// 27 | /// Validates the slot signature (`STORAGE_SLOT_SIG`) and reads the `capacity` field. 28 | /// 29 | /// # Errors 30 | /// - `Error::SignatureDismatch` if the signature is incorrect. 31 | /// - I/O errors if reading fails. 32 | fn read(buf: &mut T) -> Result 33 | where 34 | Self: Sized, 35 | { 36 | let mut sig = [0u8; 8]; 37 | buf.read_exact(&mut sig)?; 38 | if sig != STORAGE_SLOT_SIG { 39 | return Err(Error::SignatureDismatch); 40 | } 41 | 42 | let mut capacity = [0u8; 8usize]; 43 | buf.read_exact(&mut capacity)?; 44 | let capacity = u64::from_le_bytes(capacity); 45 | 46 | Ok(SlotHeader { capacity }) 47 | } 48 | } 49 | 50 | impl TryReadFrom for SlotHeader { 51 | /// Attempts to read a `SlotHeader` from a stream with position preservation and partial read awareness. 52 | /// 53 | /// If there are not enough bytes available to read the full header, returns `ReadStatus::NotEnoughData`. 54 | /// On success, returns `ReadStatus::Success(SlotHeader)`. 55 | /// 56 | /// # Errors 57 | /// - `Error::SignatureDismatch` if the signature is invalid. 58 | /// - I/O errors during reading or seeking. 59 | fn try_read(buf: &mut T) -> Result, Error> 60 | where 61 | Self: Sized, 62 | { 63 | let start_pos = buf.stream_position()?; 64 | let len = buf.seek(std::io::SeekFrom::End(0))? - start_pos; 65 | buf.seek(std::io::SeekFrom::Start(start_pos))?; 66 | if len < SlotHeader::ssize() { 67 | return Ok(ReadStatus::NotEnoughData( 68 | PacketHeader::ssize() - SlotHeader::ssize(), 69 | )); 70 | } 71 | 72 | let mut sig = [0u8; 8]; 73 | buf.read_exact(&mut sig)?; 74 | if sig != STORAGE_SLOT_SIG { 75 | buf.seek(std::io::SeekFrom::Start(start_pos))?; 76 | return Err(Error::SignatureDismatch); 77 | } 78 | 79 | let mut capacity = [0u8; 8usize]; 80 | buf.read_exact(&mut capacity)?; 81 | let capacity = u64::from_le_bytes(capacity); 82 | 83 | Ok(ReadStatus::Success(SlotHeader { capacity })) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /brec/src/traits/props/mod.rs: -------------------------------------------------------------------------------- 1 | mod byte_block; 2 | 3 | pub use byte_block::*; 4 | 5 | use crate::payload::{PayloadEncode, PayloadEncodeReferred, PayloadHooks}; 6 | 7 | /// Trait for types that provide a static 4-byte signature. 8 | /// 9 | /// Used for identifying or tagging structures in a binary format. 10 | pub trait SignatureU32 { 11 | /// Returns a reference to a static 4-byte signature. 12 | fn sig() -> &'static [u8; 4]; 13 | } 14 | 15 | /// Trait for types that can produce a CRC32 checksum. 16 | /// 17 | /// This typically includes part of or all of the internal state of the struct. 18 | pub trait CrcU32 { 19 | /// Computes a CRC32 checksum over the relevant data and returns it as 4 bytes (little-endian). 20 | fn crc(&self) -> [u8; 4]; 21 | } 22 | 23 | /// Trait for payload types that support CRC calculation. 24 | /// 25 | /// This trait requires that the type implements: 26 | /// - `PayloadEncode`: the main payload encoding logic 27 | /// - `PayloadEncodeReferred`: possibly optimized or referred encoding 28 | /// - `PayloadHooks`: any pre/post encode hooks 29 | /// 30 | /// The CRC is always returned as a `ByteBlock::Len4`. 31 | pub trait PayloadCrc 32 | where 33 | Self: PayloadEncode + PayloadHooks + PayloadEncodeReferred, 34 | { 35 | /// Computes CRC32 of the encoded payload. 36 | /// 37 | /// If referred encoding is available, it is used; otherwise, regular encoding is used. 38 | /// 39 | /// # Returns 40 | /// A 4-byte `ByteBlock` containing the CRC checksum. 41 | fn crc(&self) -> std::io::Result { 42 | let mut hasher = crc32fast::Hasher::new(); 43 | if let Some(bytes) = PayloadEncodeReferred::encode(self)? { 44 | hasher.update(bytes); 45 | } else { 46 | hasher.update(&PayloadEncode::encode(self)?); 47 | } 48 | Ok(ByteBlock::Len4(hasher.finalize().to_le_bytes())) 49 | } 50 | fn crc_size() -> usize { 51 | 4 52 | } 53 | } 54 | 55 | /// Trait for types that can return a payload signature dynamically. 56 | /// 57 | /// Signature is returned as a `ByteBlock`. 58 | pub trait PayloadSignature { 59 | /// Returns the dynamic payload signature as a byte block. 60 | fn sig(&self) -> ByteBlock; 61 | } 62 | 63 | /// Trait for types that define a static payload signature. 64 | /// 65 | /// Signature is returned as a `ByteBlock` and is constant for the type. 66 | pub trait StaticPayloadSignature { 67 | /// Returns the static signature as a byte block. 68 | fn ssig() -> ByteBlock; 69 | } 70 | 71 | /// Trait for types with a known, constant serialized size. 72 | pub trait StaticSize { 73 | /// Returns the fixed size (in bytes) of the type. 74 | fn ssize() -> u64; 75 | } 76 | 77 | /// Trait for types that can report their serialized size at runtime. 78 | pub trait Size { 79 | /// Returns the size (in bytes) of the instance. 80 | fn size(&self) -> u64; 81 | } 82 | 83 | /// Trait for payload types that return size as a `Result`. 84 | /// 85 | /// This accounts for I/O or encoding-related failures during size calculation. 86 | pub trait PayloadSize { 87 | /// Returns the total size (in bytes) of the payload. 88 | /// 89 | /// # Errors 90 | /// Returns an I/O error if size computation fails. 91 | fn size(&self) -> std::io::Result; 92 | } 93 | -------------------------------------------------------------------------------- /brec_macros/src/codegen/base/block.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | 5 | impl Base for Block { 6 | fn gen(&self) -> Result { 7 | let referred_name = self.referred_name(); 8 | let block_name = self.name(); 9 | let mut struct_fields: Vec = Vec::new(); 10 | for field in self.fields.iter() { 11 | let visibility = field.vis_token()?; 12 | let inner = if matches!(field.ty, Ty::Blob(..)) { 13 | field.referenced_ty() 14 | } else { 15 | field.direct_ty() 16 | }; 17 | struct_fields.push(quote! { 18 | #visibility #inner 19 | }); 20 | } 21 | let derefed = self 22 | .fields 23 | .iter() 24 | .filter(|f| !f.injected) 25 | .map(|f| { 26 | let field = format_ident!("{}", f.name); 27 | let field_path = if matches!(f.ty, Ty::Blob(..)) { 28 | quote! { 29 | *block.#field 30 | } 31 | } else { 32 | quote! { 33 | block.#field 34 | } 35 | }; 36 | quote! { 37 | #field: #field_path, 38 | } 39 | }) 40 | .collect::>(); 41 | let const_sig = self.const_sig_name(); 42 | let sig = self.sig(); 43 | let sig_len = self.sig_len(); 44 | let vis = self.vis_token()?; 45 | Ok(quote! { 46 | 47 | #[repr(C)] 48 | #[derive(Debug)] 49 | #vis struct #referred_name <'a> 50 | where Self: Sized 51 | { 52 | #(#struct_fields)* 53 | } 54 | 55 | impl<'a> From<#referred_name <'a>> for #block_name { 56 | fn from(block: #referred_name <'a>) -> Self { 57 | #block_name { 58 | #(#derefed)* 59 | } 60 | } 61 | } 62 | 63 | const #const_sig: [u8; #sig_len] = #sig; 64 | 65 | impl brec::SignatureU32 for #referred_name <'_> { 66 | 67 | fn sig() -> &'static [u8; #sig_len] { 68 | &#const_sig 69 | } 70 | 71 | } 72 | 73 | }) 74 | } 75 | } 76 | 77 | impl Gen for Block { 78 | fn gen(&self) -> Result { 79 | let base = Base::gen(self)?; 80 | let read = Read::gen(self)?; 81 | let read_slice = ReadFromSlice::gen(self)?; 82 | let try_read = TryRead::gen(self)?; 83 | let try_read_buffered = TryReadBuffered::gen(self)?; 84 | let crc = Crc::gen(self)?; 85 | let size = Size::gen(self); 86 | let write = Write::gen(self)?; 87 | let write_vec = WriteVectored::gen(self)?; 88 | Ok(quote! { 89 | #base 90 | #crc 91 | #size 92 | #read 93 | #read_slice 94 | #try_read 95 | #try_read_buffered 96 | #write 97 | #write_vec 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /brec_macros/src/tests/project/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | env, 4 | fs::{self, File}, 5 | io::{self, Write}, 6 | path::PathBuf, 7 | }; 8 | 9 | use crate::tests::*; 10 | use proc_macro2::TokenStream; 11 | use proptest::prelude::*; 12 | use quote::{format_ident, quote}; 13 | use uuid::Uuid; 14 | 15 | struct GeneratedProject { 16 | pub blocks: Vec, 17 | pub name: Uuid, 18 | } 19 | 20 | impl GeneratedProject { 21 | pub fn write(&self) -> io::Result<()> { 22 | let root = self.root(); 23 | let tests = root.join("../gen_tests"); 24 | if !tests.exists() { 25 | fs::create_dir(&tests)?; 26 | } 27 | let proj = tests.join(self.name.to_string()); 28 | let src = proj.join("src"); 29 | fs::create_dir(&proj)?; 30 | fs::create_dir(&src)?; 31 | let mut file = File::create(proj.join("Cargo.toml"))?; 32 | file.write_all(self.cargo_toml().as_bytes())?; 33 | let mut file = File::create(proj.join("build.rs"))?; 34 | file.write_all(self.build_rs().as_bytes())?; 35 | let mut file = File::create(src.join("main.rs"))?; 36 | file.write_all(self.main_rs().as_bytes())?; 37 | Ok(()) 38 | } 39 | pub fn root(&self) -> PathBuf { 40 | PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set")) 41 | } 42 | pub fn main_rs(&self) -> String { 43 | let decs = self 44 | .blocks 45 | .iter() 46 | .map(|blk| blk.as_dec()) 47 | .collect::>(); 48 | let mut vars = Vec::new(); 49 | let insts = self 50 | .blocks 51 | .iter() 52 | .map(|blk| { 53 | let name = format_ident!("inst_{}", blk.name); 54 | vars.push(name.clone()); 55 | let val = blk.as_val(); 56 | quote! { let #name = #val;} 57 | }) 58 | .collect::>(); 59 | quote! { 60 | use brec::*; 61 | 62 | #(#decs)* 63 | 64 | fn main() { 65 | #(#insts)* 66 | } 67 | } 68 | .to_string() 69 | } 70 | pub fn cargo_toml(&self) -> String { 71 | r#"[package] 72 | name = "test_case" 73 | version = "0.0.0" 74 | edition = "2021" 75 | 76 | [dependencies] 77 | brec = { path = "../../brec"} 78 | 79 | [build-dependencies] 80 | brec = { path = "../../brec", features=["build"]}"# 81 | .to_string() 82 | } 83 | pub fn build_rs(&self) -> String { 84 | r#"fn main() { 85 | brec::build_setup(); 86 | }"# 87 | .to_string() 88 | } 89 | } 90 | 91 | proptest! { 92 | #![proptest_config(ProptestConfig { 93 | max_shrink_iters: 50, 94 | ..ProptestConfig::with_cases(5) 95 | })] 96 | 97 | 98 | #[test] 99 | fn test(mut blocks in proptest::collection::vec(GeneratedBlock::arbitrary(), 1..20)) { 100 | let mut seen = HashSet::new(); 101 | blocks.retain(|blk| seen.insert(blk.name.clone())); 102 | let pro = GeneratedProject { blocks, name: Uuid::new_v4()}; 103 | pro.write()?; 104 | // for blk in blks.into_iter() { 105 | 106 | // } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /brec_macros/src/collector/mod.rs: -------------------------------------------------------------------------------- 1 | mod blocks; 2 | mod packet; 3 | mod payloads; 4 | 5 | use std::{ 6 | collections::HashMap, 7 | env, 8 | fs::File, 9 | io::Write, 10 | path::PathBuf, 11 | sync::{Mutex, MutexGuard}, 12 | }; 13 | 14 | use crate::*; 15 | 16 | use lazy_static::lazy_static; 17 | use quote::quote; 18 | 19 | lazy_static! { 20 | static ref COLLECTOR: Mutex = Mutex::new(Collector::default()); 21 | } 22 | 23 | pub fn get_pkg_name() -> String { 24 | std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_string()) 25 | } 26 | 27 | #[derive(Debug, Default)] 28 | pub struct Collector { 29 | blocks: HashMap>, 30 | payloads: HashMap>, 31 | } 32 | 33 | impl Collector { 34 | pub fn get<'a>() -> Result, E> { 35 | COLLECTOR.lock().map_err(|_| E::NoAccessToCollector) 36 | } 37 | pub fn add_block(&mut self, block: Block) -> Result<(), E> { 38 | let blocks = self.blocks.entry(get_pkg_name()).or_default(); 39 | let fname = block.fullname()?.to_string(); 40 | blocks.entry(fname).or_insert(block); 41 | Ok(()) 42 | } 43 | pub fn add_payload(&mut self, payload: Payload) -> Result<(), E> { 44 | let payloads = self.payloads.entry(get_pkg_name()).or_default(); 45 | let fname = payload.fullname()?.to_string(); 46 | payloads.entry(fname).or_insert(payload); 47 | Ok(()) 48 | } 49 | pub fn is_blocks_empty(&mut self) -> bool { 50 | self.blocks.entry(get_pkg_name()).or_default().is_empty() 51 | } 52 | pub fn is_payloads_empty(&mut self) -> bool { 53 | self.payloads.entry(get_pkg_name()).or_default().is_empty() 54 | } 55 | pub fn write(&mut self, cfg: &Config) -> Result<(), E> { 56 | let pkg_name = get_pkg_name(); 57 | let block = if self.is_blocks_empty() { 58 | quote! {} 59 | } else { 60 | blocks::gen( 61 | self.blocks 62 | .entry(pkg_name.clone()) 63 | .or_default() 64 | .values() 65 | .collect::>(), 66 | cfg, 67 | )? 68 | }; 69 | let payload = if self.is_payloads_empty() && cfg.is_no_default_payloads() { 70 | quote! {} 71 | } else { 72 | payloads::gen( 73 | self.payloads 74 | .entry(pkg_name) 75 | .or_default() 76 | .values() 77 | .collect::>(), 78 | cfg, 79 | )? 80 | }; 81 | let packet = if self.is_blocks_empty() 82 | || (self.is_payloads_empty() && cfg.is_no_default_payloads()) 83 | { 84 | quote! {} 85 | } else { 86 | packet::gen()? 87 | }; 88 | let output = quote! { 89 | #block 90 | #payload 91 | #packet 92 | }; 93 | let out_dir = env::var("OUT_DIR")?; 94 | let path = PathBuf::from(out_dir).join("brec.rs"); 95 | let mut file = File::create(&path)?; 96 | file.write_all(output.to_string().as_bytes())?; 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /brec/src/traits/props/byte_block.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// A fixed-size byte buffer supporting several predefined lengths. 4 | /// 5 | /// `ByteBlock` is used for representing binary data chunks with known allowed sizes. 6 | /// It provides utility methods for accessing the internal data as a slice, 7 | /// validating capacity, and converting from `Vec` safely. 8 | #[derive(PartialEq, Eq, Debug)] 9 | pub enum ByteBlock { 10 | /// 4-byte buffer. 11 | Len4([u8; 4]), 12 | /// 8-byte buffer. 13 | Len8([u8; 8]), 14 | /// 16-byte buffer. 15 | Len16([u8; 16]), 16 | /// 32-byte buffer. 17 | Len32([u8; 32]), 18 | /// 64-byte buffer. 19 | Len64([u8; 64]), 20 | /// 128-byte buffer. 21 | Len128([u8; 128]), 22 | } 23 | 24 | impl ByteBlock { 25 | /// Returns the internal byte array as a slice. 26 | /// 27 | /// # Returns 28 | /// A reference to the byte array stored in the `ByteBlock` 29 | pub fn as_slice(&self) -> &[u8] { 30 | match self { 31 | ByteBlock::Len4(arr) => arr, 32 | ByteBlock::Len8(arr) => arr, 33 | ByteBlock::Len16(arr) => arr, 34 | ByteBlock::Len32(arr) => arr, 35 | ByteBlock::Len64(arr) => arr, 36 | ByteBlock::Len128(arr) => arr, 37 | } 38 | } 39 | 40 | /// Returns the number of bytes stored in this block. 41 | pub fn size(&self) -> usize { 42 | self.as_slice().len() 43 | } 44 | 45 | /// Validates that the given capacity is allowed for a `ByteBlock`. 46 | /// 47 | /// Allowed sizes: 4, 8, 16, 32, 64, 128. 48 | /// 49 | /// # Arguments 50 | /// * `cap` – Capacity in bytes to validate. 51 | /// 52 | /// # Returns 53 | /// `Ok(())` if the capacity is valid, or an `Error::InvalidCapacity` otherwise. 54 | pub fn is_valid_capacity(cap: u8) -> Result<(), Error> { 55 | if [4, 8, 16, 32, 64, 128].contains(&cap) { 56 | Ok(()) 57 | } else { 58 | Err(Error::InvalidCapacity( 59 | cap as usize, 60 | "4, 8, 16, 32, 64, 128".to_string(), 61 | )) 62 | } 63 | } 64 | } 65 | 66 | impl TryFrom> for ByteBlock { 67 | type Error = Error; 68 | 69 | /// Attempts to convert a `Vec` into a `ByteBlock` of matching length. 70 | /// 71 | /// # Arguments 72 | /// * `value` – The vector to convert. Length must be one of the supported sizes. 73 | /// 74 | /// # Returns 75 | /// A corresponding `ByteBlock` variant on success, or an error on failure: 76 | /// - `Error::InvalidCapacity` if the size is not supported. 77 | /// - `Error::FailExtractByteBlock` if conversion fails unexpectedly. 78 | fn try_from(value: Vec) -> Result { 79 | match value.len() { 80 | 4 => value.try_into().map(Self::Len4), 81 | 8 => value.try_into().map(Self::Len8), 82 | 16 => value.try_into().map(Self::Len16), 83 | 32 => value.try_into().map(Self::Len32), 84 | 64 => value.try_into().map(Self::Len64), 85 | 128 => value.try_into().map(Self::Len128), 86 | invalid => { 87 | return Err(Error::InvalidCapacity( 88 | invalid, 89 | "4, 8, 16, 32, 64, 128".to_string(), 90 | )) 91 | } 92 | } 93 | .map_err(|_| Error::FailExtractByteBlock) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /site/docs/parts/packets.md: -------------------------------------------------------------------------------- 1 | 2 | Users do not need to define possible packet types since any combination of blocks (up to 255) and a single optional payload constitutes a valid packet. 3 | 4 | ```rust 5 | brec::generate!(); 6 | 7 | let my_packet = Packet::new( 8 | // You are limited to 255 blocks per packet. 9 | vec![ 10 | Block::MyBlockA(MyBlockA::default()), 11 | Block::MyBlockC(MyBlockC::default()) 12 | ], 13 | // Note: payload is optional 14 | Some(Payload::MyPayloadA(MyPayloadA::default())) 15 | ); 16 | ``` 17 | 18 | ### Packet Constraints 19 | 20 | - A packet can contain **0 to 255 blocks**. 21 | - A packet can include **0 or 1 payload**. 22 | 23 | **Warning!** In most cases, having 1-5 blocks per packet is more than sufficient. A significant number of blocks can lead to an increase in compilation time but will not affect the performance of the compiled code. Therefore, if compilation time is a critical factor, it is recommended to avoid a large number of blocks in packets. 24 | 25 | To clarify, **runtime performance is not affected**, but the compilation time increases because the compiler has to generate multiple implementations for generic types used in `PacketDef` (an internal `brec` structure). 26 | 27 | ### Packet Trait Implementations 28 | 29 | A `Packet` can be used as a standalone unit for data exchange. It implements the following traits: 30 | 31 | | Trait | Method | Return Type | Description | 32 | |-----------------------|--------|-------------|-------------| 33 | | `ReadFrom` | `read(buf: &mut T)` | `Result` | Attempts to read a packet from a source. | 34 | | `TryReadFrom` | `try_read(buf: &mut T)` | `Result, Error>` | Attempts to read a packet, but if data is insufficient, it returns a corresponding read status instead of an error. Also, moves the source’s position only upon successful reading; otherwise, it remains unchanged. | 35 | | `TryReadFromBuffered` | `try_read(reader: &mut T)` | `Result, Error>` | Identical to `TryReadFrom`. | 36 | | `WriteMutTo` | `write(&mut self, buf: &mut T)` | `std::io::Result` | Equivalent to the standard `write` method, returning the number of bytes written. Does not guarantee that data is flushed to the output, so calling `flush` is required if such guarantees are needed. | 37 | | `WriteMutTo` | `write_all(&mut self, buf: &mut T)` | `std::io::Result<()>` | Equivalent to the standard `write_all` method. | 38 | | `WriteVectoredMutTo` | `slices(&mut self)` | `std::io::Result` | Returns the binary representation of the packet as slices. | 39 | | `WriteVectoredMutTo` | `write_vectored(&mut self, buf: &mut T)` | `std::io::Result` | Attempts a vectored write of the packet (analogous to the standard `write_vectored`). | 40 | | `WriteVectoredMutTo` | `write_vectored_all(&mut self, buf: &mut T)` | `std::io::Result<()>` | Attempts a vectored write of the packet (analogous to the standard `write_vectored_all`). | 41 | 42 | ### Packet Filtering 43 | 44 | `Packet` provides a highly useful method: 45 | 46 | ```rust 47 | filtered( 48 | reader: &mut R, 49 | rules: &Rules 50 | ) -> Result, Error> 51 | ``` 52 | 53 | This method allows you to "peek" into a packet before processing the payload, which can significantly improve performance when filtering specific packets. 54 | -------------------------------------------------------------------------------- /brec_macros/src/collector/payloads/props.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | pub fn encode(payloads: &[&Payload]) -> Result { 7 | let mut variants = Vec::new(); 8 | for payload in payloads.iter() { 9 | let fullname = payload.fullname()?; 10 | variants.push(quote! {Payload::#fullname(pl) => brec::PayloadEncode::encode(pl)}); 11 | } 12 | Ok(quote! { 13 | impl brec::PayloadEncode for Payload { 14 | fn encode(&self) -> std::io::Result> { 15 | match self { 16 | #(#variants,)* 17 | Payload::Bytes(pl) => brec::PayloadEncode::encode(pl), 18 | Payload::String(pl) => brec::PayloadEncode::encode(pl), 19 | } 20 | } 21 | } 22 | }) 23 | } 24 | 25 | pub fn encode_referred(payloads: &[&Payload]) -> Result { 26 | let mut variants = Vec::new(); 27 | for payload in payloads.iter() { 28 | let fullname = payload.fullname()?; 29 | variants.push(quote! {Payload::#fullname(pl) => brec::PayloadEncodeReferred::encode(pl)}); 30 | } 31 | Ok(quote! { 32 | impl brec::PayloadEncodeReferred for Payload { 33 | fn encode(&self) -> std::io::Result> { 34 | match self { 35 | #(#variants,)* 36 | Payload::Bytes(pl) => brec::PayloadEncodeReferred::encode(pl), 37 | Payload::String(pl) => brec::PayloadEncodeReferred::encode(pl), 38 | } 39 | } 40 | } 41 | }) 42 | } 43 | 44 | pub fn sig(payloads: &[&Payload]) -> Result { 45 | let mut variants = Vec::new(); 46 | for payload in payloads.iter() { 47 | let fullname = payload.fullname()?; 48 | variants.push(quote! {Payload::#fullname(pl) => pl.sig()}); 49 | } 50 | Ok(quote! { 51 | impl brec::PayloadSignature for Payload { 52 | fn sig(&self) -> brec::ByteBlock { 53 | match self { 54 | #(#variants,)* 55 | Payload::Bytes(pl) => pl.sig(), 56 | Payload::String(pl) => pl.sig(), 57 | } 58 | } 59 | } 60 | }) 61 | } 62 | 63 | pub fn crc(payloads: &[&Payload]) -> Result { 64 | let mut variants = Vec::new(); 65 | for payload in payloads.iter() { 66 | let fullname = payload.fullname()?; 67 | variants.push(quote! {Payload::#fullname(pl) => pl.crc()}); 68 | } 69 | Ok(quote! { 70 | impl brec::PayloadCrc for Payload { 71 | fn crc(&self) -> std::io::Result { 72 | match self { 73 | #(#variants,)* 74 | Payload::Bytes(pl) => pl.crc(), 75 | Payload::String(pl) => pl.crc(), 76 | } 77 | } 78 | } 79 | }) 80 | } 81 | 82 | pub fn size(payloads: &[&Payload]) -> Result { 83 | let mut variants = Vec::new(); 84 | for payload in payloads.iter() { 85 | let fullname = payload.fullname()?; 86 | variants.push(quote! {Payload::#fullname(pl) => pl.size()}); 87 | } 88 | Ok(quote! { 89 | impl brec::PayloadSize for Payload { 90 | fn size(&self) -> std::io::Result { 91 | match self { 92 | #(#variants,)* 93 | Payload::Bytes(pl) => pl.size(), 94 | Payload::String(pl) => pl.size(), 95 | } 96 | } 97 | } 98 | }) 99 | } 100 | --------------------------------------------------------------------------------