Bendy
A rust library for encoding and decoding bencode with enforced cannonicalization rules.
Install / Use
/learn @P3KI/BendyREADME
Bendy
A Rust library for encoding and decoding bencode with enforced canonicalization rules. It also provides some facilities for reflecting over arbitrary decoded bencode structures. Bencode is a simple but very effective encoding scheme, originating with the BitTorrent peer-to-peer system.
You may be looking for:
- Known Alternatives
- Why should I use it
- Usage
- Feature Flags
- Encoding
- Decoding
- Serde Support
- Reflection
- Unsafe Code
- Contributing
Known alternatives:
This is not the first library to implement Bencode. In fact there are several implementations already:
- Toby Padilla serde-bencode
- Arjan Topolovec's rust-bencode,
- Murarth's bencode,
- and Jonas Hermsmeier's rust-bencode
Why should I use it?
So why the extra work adding yet-another-version of a thing that already exists, you might ask?
Enforced correctness
Implementing a canonical encoding form is straight forward. It comes down to defining a proper way of handling unordered data. The next step is that bendy's sorting data before encoding it using the regular Bencode rules. If your data is already sorted bendy will of course skip the extra sorting step to gain efficiency. But bendy goes a step further to ensure correctness: If you hand the library data that you say is already sorted, bendy still does an in-place verification to ensure that your data actually is sorted and complains if it isn't. In the end, once bendy serialized your data, it's Bencode through and through. So it's perfectly compatible with every other Bencode library.
Just remember: At this point only bendy enforces the correctness of the canonical format if you read it back in.
Canonical representation
Bendy ensures that any de-serialize / serialize round trip produces the exact same and correct binary representation. This is relevant if you're dealing with unordered sets or map-structured data where theoretically the order is not relevant, but in practice it is, especially if you want to ensure that cryptographic signatures related to the data structure do not get invalidated accidentally.
| Data Structure | Default Impl | Comment |
|----------------|--------------|--------------------------------------------------------------------------------------------|
| Vec | ✔ | Defines own ordering |
| VecDeque | ✔ | Defines own ordering |
| LinkedList | ✔ | Defines own ordering |
| HashMap | ✔ | Ordering missing but content is ordered by key byte representation. |
| BTreeMap | ✔ | Defines own ordering |
| HashSet | ✘ | (Unordered) Set handling not yet defined |
| BTreeSet | ✘ | (Unordered) Set handling not yet defined |
| BinaryHeap | ✘ | Ordering missing |
| Iterator | ~ | emit_unchecked_list() allows to emit any iterable but user needs to ensure the ordering. |
Attention:
-
Since most list types already define their inner ordering, data structures like
Vec,VecDeque, andLinkedListwill not get sorted during encoding! -
There is no default implementation for handling generic iterators. This is by design.
Bendycannot tell from an iterator whether the underlying structure requires sorting or not and would have to take data as-is.
Usage
First you need to add bendy as a project dependency:
[dependencies]
bendy = "^0.4"
Feature flags
Bendy has the following feature flags:
std- Enabled by default.serde- Support serde. Requiresstdto be enabled.inspect- Include reflection facilities.
Encoding with ToBencode
To encode an object of a type which already implements the ToBencode trait
it is enough to import the trait and call the to_bencode() function on the object.
use bendy::encoding::{ToBencode, Error};
let my_data = vec!["hello", "world"];
let encoded = my_data.to_bencode()?;
assert_eq!(b"l5:hello5:worlde", encoded.as_slice());
Ok::<(), Error>(())
Implementing ToBencode
In most cases it should be enough to overwrite the associated encode function
and keep the default implementation of to_bencode.
The function will provide you with a SingleItemEncoder which must be used to
emit any relevant components of the current object. As long as these implement
ToBencode themselves it is enough to pass them into the emit function of
the encoder as this will serialize any type implementing the trait.
Next to emit the encoder also provides a list of functions to encode specific
bencode primitives (i.e. emit_int and emit_str) and nested bencode elements
(i.e. emit_dict and emit_list). These methods should be used if its necessary
to output a specific non default data type.
Implementing Integer Encoding
As bencode has native integer support bendy provides default implementations for
all of rusts native integer types. This allows to call to_bencode on any integer
object and to pass these objects into the encoder's emit_int function.
use bendy::encoding::{ToBencode, SingleItemEncoder, Error};
struct IntegerWrapper(i64);
impl ToBencode for IntegerWrapper {
const MAX_DEPTH: usize = 0;
fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
encoder.emit_int(self.0)
}
}
let example = IntegerWrapper(21);
let encoded = example.to_bencode()?;
assert_eq!(b"i21e", encoded.as_slice());
let encoded = 21.to_bencode()?;
assert_eq!(b"i21e", encoded.as_slice());
Ok::<(), Error>(())
Encode a byte string
Another data type bencode natively supports are byte strings. Therefore bendy
provides default implementations for String and &str.
use bendy::encoding::{ToBencode, SingleItemEncoder, Error};
struct StringWrapper(String);
impl ToBencode for StringWrapper {
const MAX_DEPTH: usize = 0;
fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
encoder.emit_str(&self.0)
}
}
let example = StringWrapper("content".to_string());
let encoded = example.to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());
let encoded = "content".to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());
Ok::<(), Error>(())
As its a very common pattern to represent a byte string as Vec<u8> bendy
exposes the AsString wrapper. This can be used to encapsulate any element
implementing AsRef<[u8]> to output itself as a bencode string instead of a
list.
use bendy::encoding::{ToBencode, SingleItemEncoder, Error, AsString};
struct ByteStringWrapper(Vec<u8>);
impl ToBencode for ByteStringWrapper {
const MAX_DEPTH: usize = 0;
fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
let content = AsString(&self.0);
encoder.emit(&content)
}
}
let example = ByteStringWrapper(b"content".to_vec());
let encoded = example.to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());
let encoded = AsString(b"content").to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());
Ok::<(), Error>(())
Encode a dictionary
If a data structure contains key-value pairs its most likely a good idea to encode it as a bencode dictionary. This is also true for most structs with more then one member as it might be helpful to represent their names to ensure the existence of specific (optional) member.
Attention: To ensure a canonical representation bendy requires that the keys
of a dictionary emitted via emit_dict are sorted in ascending order or the
encoding will fail with an error of kind UnsortedKeys. In case of an unsorted
dictionary it might be useful to use emit_and_sort_dict instead.
use bendy::encoding::{ToBencode, SingleItemEncoder, Error};
struct Example {
label: String,
counter: u64,
}
impl ToBencode for Example {
const MAX_DEPTH: usize = 1;
fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
encoder.emit_dict(|mut e| {
e.emit_pair(b"counter", &self.counter)?;
e.emit_pair(b"label", &self.label)?;
Ok(())
})
}
}
let example = Example { label: "Example".to_string(), counter: 0 };
let encoded = example.to_bencode()?;
assert_eq!(b"d7:counteri0e5:label7:Examplee", encoded.as_slice());
Ok::<(), Error>(())
Encode a list
While encoding a list bendy assumes the elements inside this list are inherently sorted through their position inside the list. The implementation is therefore free to choose its own sorting.
use bendy::encoding::{ToBencode, SingleItemEncoder, Error};
struct Location(i64, i64);
impl ToBencode for Locati
