# `briny`
`briny` is one of the only Rust crates that enforces binary trust boundaries at compile time - zero unsafe, no-alloc, no-macro.
`briny` gives you airtight control over what data is trusted and when. It helps you securely parse, validate, and serialize binary-structured data without ever trusting unchecked input.
## What Makes `briny` Different?
`briny` enforces Zero Trust Architecture (ZTA) principles at compile time. Just like Rust's ownership system prevents memory safety bugs before runtime, `briny` prevents logic from touching untrusted or unvalidated input. No hopeful parsing, no runtime footguns.
### If you follow `briny`'s rules
- All external data must pass `Validate` before use
- All deserialized structures are wrapped in `TrustedData`
- No logic can access unchecked input without being explicit
### If you *don't* follow the rules
- It's like misusing `unsafe {}` - *you opt out of the safety net*
- `briny` won't stop you from writing broken or insecure `Validate` impls
- You can still violate trust boundaries *after* validation, if you ignore discipline
- `briny` can't enforce runtime misuse beyond the type system.
## Why Use `briny`
- Enforce trust boundaries with marker traits (`Trusted`, `Untrusted`)
- Zero dependencies and `#![no_std]` compatible - no `alloc` either
- Built for embedded, security-critical, and sandboxed Rust systems
- Prevent bugs before you even test for them
## Features
- Zero Trust Architecture (ZTA)–aligned
- Binary-safe serialization via `Pack`/`Unpack`
- Trusted vs. untrusted data split at the type level
- Fixed-size byte buffer abstraction (`ByteBuf<T, N>`)
- Dependency-free (no `std` or `alloc`)
### Trust Model
`briny` enforces explicit trust boundaries using:
- `UntrustedData<T>`: Marker for unsafe input (from users, network, disk, etc.)
- `Validate`: Trait that defines the rules for converting untrusted data into trusted form
- `TrustedData<T>`: Guarantees validation has occurred - only safe data gets in
- Sealed trait `Trusted` ensures trust cannot be used outside the crate
This ZTA-style model improves security on many frontiers, meaning:
- No unchecked logic runs on untrusted data
- All transitions are explicit and type-checked
- You *can't* forget to validate
## Example
```rust
use briny::prelude::*;
struct Data([u8; 4]);
impl Validate for Data {
fn validate(&self) -> Result<(), ValidationError> {
if self.0[0] == 42 { Ok(()) } else { Err(ValidationError) }
}
}
impl Pack for Data {
fn pack(&self, mut out: PackRef<'_>) -> Result<(), ValidationError> {
out.ref_mut().copy_from_slice(&self.0);
Ok(())
}
}
impl Unpack for Data {
fn unpack_and_validate(input: UnpackBuf<'_>) -> Result<TrustedData<'_, Self>, ValidationError> {
let slice = input.as_slice();
if slice.len() != 4 {
return Err(ValidationError);
}
let data = Data([slice[0], slice[1], slice[2], slice[3]]);
TrustedData::new(data)
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// random bytes
let external = UntrustedData::new([42, 0, 0, 0]);
// validate the payload
let trusted = TrustedData::new(my.trust()?.into_inner())?;
// serialize it
let mut buf = [0u8; 4];
trusted.pack(PackRef::new(&mut buf))?;
assert_eq!(buf, [42, 0, 0, 0]);
Ok(())
}
```
## Comparison
| Compile-time security guarantees | N | N | N | Y |
| Blocks parsing before validation | N | N | N | Y |
| Validation enforced by compiler | N | N | N | Y |
| Type-level trust separation | N | N | N | Y |
| `no_std` compatible | ~ | N | Y | Y |
| Accidental bypasses impossible | N | N | N | Y |
### What `briny` Does Not Do
While `briny` enforces trust boundaries at compile time, it's not a one-size-fits-all validation framework. It doesn't...
- Parse or validate complex or nested data formats like JSON, XML, or YAML.
- Handle cryptographic operations or key management.
- Provide runtime-configurable validation rules or dynamic schema updates.
- Offer detailed validation error reporting with rich diagnostics.
- Support heap allocations or complex data structures requiring `std` or `alloc`.
Use `briny` when you need binary-safe, zero-cost, compile-time enforced trust for fixed-layout, embedded, or low-level data structures.
For everything else-especially rich data formats or dynamic validation-consider combining `briny` with crates like `serde`, `validator`, or `nom`.
Here is a comparison between `briny`, `serde`, `validator`, and `nom` where each must validate a 4-byte array where the first byte is 42.
#### `serde` - Deserialization without enforced validation
```rust
use serde::Deserialize;
#[derive(Deserialize)]
struct Data([u8; 4]);
// deserialize blindly, even if data is bad
let my: Data = bincode::deserialize(&input_bytes)?;
// no guarantee this is safe!
assert_eq!(my.0[0], 42); // Could panic or be wrong
```
Risk: Data is used before it's validated. The deserialized value is implicitly trusted.
#### `validator` - Runtime validation, trust still implicit
```rust
use validator::{Validate};
#[derive(Validate)]
struct Data {
#[validate(custom = "validate_first_byte")]
data: [u8; 4],
}
fn validate_first_byte(data: &[u8; 4]) -> Result<(), validator::ValidationError> {
if data[0] == 42 { Ok(()) } else { Err(ValidationError::new("bad")) }
}
// data is deserialized before it's validated
let my: Data = serde_json::from_str(json_input)?;
my.validate()?; // You must remember to call this!
```
Risk: Forgeting to call `.validate()` could end horrifically; everything is runtime-based.
#### `nom` - Binary parsing with separate validation
```rust
use nom::{bytes::complete::take, IResult};
fn parse(input: &[u8]) -> IResult<&[u8], [u8; 4]> {
let (rest, bytes) = take(4usize)(input)?;
Ok((rest, [bytes[0], bytes[1], bytes[2], bytes[3]]))
}
// parse succeeds regardless of content
let (_, my_data) = parse(&input_bytes)?;
if my_data[0] != 42 {
return Err("bad");
}
```
Risk: Parsing and validation are disconnected. It's easy to skip checks.
#### `briny` - Enforced trust boundaries at the type level
```rust
use briny::prelude::*;
struct Data([u8; 4]);
impl Validate for Data {
fn validate(&self) -> Result<(), ValidationError> {
if self.0[0] == 42 { Ok(()) } else { Err(ValidationError) }
}
}
impl Unpack for Data {
fn unpack_and_validate(input: UnpackBuf<'_>) -> Result<TrustedData<'_, Self>, ValidationError> {
let slice = input.as_slice();
if slice.len() != 4 { return Err(ValidationError); }
TrustedData::new(Data([slice[0], slice[1], slice[2], slice[3]]))
}
}
// from raw input to trusted, validated value
let buf = UnpackBuf::new(&input_bytes);
let trusted: TrustedData<'_, Data> = Data::unpack_and_validate(buf)?;
// cannot access trusted logic until valid
trusted.as_ref(); // fully safe
```
Guaranteed: Data must be validated before it compiles. No unsafe access is even possible without going through the Validate gate.
## Raw Bytes
Use `ByteBuf<T, N>` to handle fixed-size byte arrays before parsing:
```rust
use briny::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = ByteBuf::<u32, 4>::new(42u32.to_le_bytes());
let _parsed = input.parse()?; // validated and parsed as u32
Ok(())
}
```
This is useful when reading from sockets, files, or hardware registers.
### Prelude
`briny` provides a prelude module for ergonomic imports:
```rust
use briny::prelude::*;
// brings in: Validate, TrustedData, UntrustedData, Pack, Unpack, etc.
```
This crate is #![no_std], fully portable, and ideal for embedded and security-critical systems.
## Project Status
- Security-first API design
- 100% safe Rust (no unsafe)
- Fully tested (integration and unit tests)
- No dependencies
- `#![no_std]` support
- Not dependent on `alloc`
- Community audits welcome
## Contributing
Contributions, bug reports, and suggestions are welcome! This project aims to help build verifiably secure foundations for low-level and embedded Rust development.
### License
`briny` is under an MIT license.