# babbel_bencode
A Rust library for parsing, constructing, and converting Bencode data. Designed for embedded systems, resource-constrained environments, and general-purpose use. Supports round-tripping Bencode and conversion to JSON, YAML, XML, and TOML.
[](https://github.com/clockworkengineer/bencode)
[](../LICENSE)
[](Cargo.toml)
[](https://buymeacoffee.com/roberttizz1)
## Features
- Parse Bencode into a typed tree (`Node`)
- Serialize `Node` back to canonical Bencode
- Convert `Node` to JSON, YAML, XML, or TOML (optional Cargo features)
- Zero-copy borrowed parsing via `BorrowedNode` — no heap allocation
- `no_std` compatible (disable the default `std` feature)
- Memory pool / arena allocation (`Arena`, `StackBuffer`, `MemoryTracker`)
- Stack-based iterative parser — safe for deeply nested structures
- Validation helpers for ergonomic field extraction (`get_required`, `get_int_required`, …)
- Configurable parsing depth (`ParserConfig`) and canonicalisation enforcement (`EncoderConfig`)
- Read/write from files or in-memory buffers
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
babbel_bencode = "0.1.0"
```
Or as a path dependency within this workspace:
```toml
[dependencies]
babbel_bencode = { path = "crates/bencode" }
```
All binary-safe streaming I/O (`IByteStream`, `FileSource`, `FileDestination`, `Buffer`) is unified with and powered by [`babbel_core::io`](../babbel_core).
To minimise binary size, disable unused format-conversion features:
```toml
[dependencies]
babbel_bencode = { version = "0.1.0", default-features = false, features = ["std", "json"] }
```
Available features: `std` (default), `json`, `toml`, `xml`, `yaml`.
## Release Builds & LTO for Optimal Size
For the smallest and fastest binaries, enable Link Time Optimization (LTO):
```toml
[profile.release]
lto = true
```
Then build with:
```sh
cargo build --release
```
## Quick Examples
### Parse a `.torrent` file and convert to YAML
```rust
use babbel_bencode::{FileSource, FileDestination, parse, to_yaml};
let mut src = FileSource::new("example.torrent")?;
let node = parse(&mut src)?;
let mut dst = FileDestination::new("example.yaml")?;
to_yaml(&node, &mut dst);
```
### Round-trip a Bencode buffer
```rust
use babbel_bencode::{parse_bytes, stringify_to_bytes};
let raw = b"d3:foo3:bar4:spamli1ei2ei3eee";
let node = parse_bytes(raw)?;
let encoded = stringify_to_bytes(&node);
assert_eq!(raw.as_slice(), encoded.as_slice());
```
### Construct a `Node` and render as JSON
```rust
use babbel_bencode::{Node, make_node, to_json, BufferDestination};
// Using the From trait with an array of key-value pairs
let node = Node::from([
("name", Node::from("hello")),
("count", Node::from(42_i64)),
]);
let mut dst = BufferDestination::new();
to_json(&node, &mut dst);
```
### Validate and extract fields ergonomically
```rust
use babbel_bencode::parse_bytes;
let node = parse_bytes(b"d4:name5:Alice3:agei30ee")?;
let name: &str = node.get_string_required("name")?;
let age: i64 = node.get_int_required("age")?;
```
## Data Model
```
Node::Integer(i64) — Bencode integer
Node::Str(String) — Bencode string (UTF-8)
Node::List(Vec<Node>) — Bencode list
Node::Dictionary(HashMap<String, Node>) — Bencode dictionary
Node::None — Empty / uninitialized node
```
Nodes implement `Clone`, `Debug`, `PartialEq`, and `Display`.
### Creating Nodes
```rust
use babbel_bencode::{Node, make_node};
use std::collections::HashMap;
// Direct variants
let i = Node::Integer(42);
let s = Node::Str("hello".to_string());
// Via the generic helper (uses From conversions)
let n = make_node(99_i64); // -> Node::Integer(99)
let n = make_node("world"); // -> Node::Str("world")
let n = make_node(vec![1_i64, 2]); // -> Node::List([Integer(1), Integer(2)])
// Array literal short-hand
let list = Node::from([1_i64, 2, 3]);
let dict = Node::from([("key", Node::Integer(1))]);
```
## API Overview
### Parsing
| `parse(&mut src)` | Parse from any `Source` (file or buffer) |
| `parse_bytes(data: &[u8])` | Parse directly from a byte slice |
| `parse_str(data: &str)` | Parse directly from a string slice |
| `parse_iterative(&mut src)` | Stack-based iterative parse (deep nesting safe) |
| `parse_bytes_iterative(data)` | Iterative parse from byte slice |
| `parse_str_iterative(data)` | Iterative parse from string slice |
| `parse_borrowed(data: &[u8])` | Zero-copy parse returning `BorrowedNode` |
| `validate_bencode(data: &[u8])` | Validate without building a node tree |
### Stringifying
| `stringify(&node, &mut dst)` | Write canonical Bencode to any `Destination` |
| `stringify_to_bytes(&node)` | Return Bencode as `Vec<u8>` |
| `stringify_to_string(&node)` | Return Bencode as `String` |
| `to_json(&node, &mut dst)` | Convert to JSON (`json` feature) |
| `to_toml(&node, &mut dst)` | Convert to TOML (`toml` feature) |
| `to_xml(&node, &mut dst)` | Convert to XML (`xml` feature) |
| `to_yaml(&node, &mut dst)` | Convert to YAML (`yaml` feature) |
### I/O
| `BufferSource` | Read Bencode from an in-memory buffer |
| `FileSource` | Read Bencode from a file (`std` feature) |
| `BufferDestination` | Write output to an in-memory buffer |
| `FileDestination` | Write output to a file (`std` feature) |
### Node Methods
**Type checking:** `is_integer()`, `is_string()`, `is_list()`, `is_dictionary()`, `is_none()`
**Value access:** `as_integer()`, `as_string()`, `as_list()`, `as_list_mut()`, `as_dictionary()`, `as_dictionary_mut()`
**Dictionary access:** `get(key)`, `get_mut(key)`
**Validation helpers:**
| `get_required(key)` | `Result<&Node, String>` |
| `get_int_required(key)` | `Result<i64, String>` |
| `get_string_required(key)` | `Result<&str, String>` |
| `get_list_required(key)` | `Result<&Vec<Node>, String>` |
| `get_dict_required(key)` | `Result<&HashMap<String, Node>, String>` |
| `get_int_optional(key)` | `Option<i64>` |
| `get_string_optional(key)` | `Option<&str>` |
| `get_list_optional(key)` | `Option<&Vec<Node>>` |
| `get_dict_optional(key)` | `Option<&HashMap<String, Node>>` |
**Utility:** `len()`, `is_empty()`, `type_name()`
### Embedded / `no_std` API
| `Arena` | Bump allocator from a fixed buffer |
| `StackBuffer<N>` | Stack-allocated byte buffer |
| `MemoryTracker` | Allocation accounting for embedded systems |
| `FixedSizeBuffer<N>` | Stack-allocated fixed-size buffer (const generic) |
| `MemoryBounds` | Const-generic memory bounds calculator |
| `BorrowedNode` | Zero-copy borrowed node (no allocation) |
### Configuration
```rust
use babbel_bencode::{ParserConfig, EncoderConfig};
let parser = ParserConfig::new().with_max_depth(50); // default: 100
let encoder = EncoderConfig::new()
.with_canonical(true) // enforce sorted dict keys, no leading zeros
.with_dict_order_verification(true);
```
### Utilities
| `version()` | Returns the library version string |
| `read_file(path)` | Read a file to `String` (`std` feature) → `Result<String, io::Error>` |
| `write_file(path, content)` | Write a string to a file (`std` feature) → `Result<(), io::Error>` |
## Error Handling
`BencodeError` is a lightweight, allocation-free enum suitable for `no_std` environments:
```rust
pub enum BencodeError {
EmptyInput,
InvalidInteger,
UnterminatedInteger,
InvalidStringLength,
StringTooShort,
UnterminatedList,
UnterminatedDictionary,
DictKeysOutOfOrder,
DictKeyMustBeString,
UnexpectedCharacter(char),
FileNotFound,
IoError,
}
```
Each variant exposes `.code() -> u8` for compact logging and `.as_str() -> &'static str` for human-readable messages. In `std` environments, `BencodeError` implements `std::error::Error`.
> **Note:** `read_file` / `write_file` return `std::io::Error`, not `BencodeError`.
## Minimum Supported Rust Version
Rust **1.85.0** (edition 2024).
## Documentation
- [`../docs/API_OVERVIEW.md`](../docs/API_OVERVIEW.md) — API reference
- [`../docs/DEVELOPER_GUIDE.md`](../docs/DEVELOPER_GUIDE.md) — contributing and project structure
- [`../docs/EMBEDDED_GUIDE.md`](../docs/EMBEDDED_GUIDE.md) — `no_std` / embedded usage
- [`../docs/CHANGELOG.md`](../docs/CHANGELOG.md) — release history
- [`../examples/README.md`](../examples/README.md) — example programs
## Support
If you find this project useful, you can support its development by buying me a coffee:
[](https://buymeacoffee.com/roberttizz1)
Or visit [buymeacoffee.com/roberttizz1](https://buymeacoffee.com/roberttizz1).
## License
MIT License. See [LICENSE](../LICENSE) for details.