Bodu.Text.Bencode
Bodu.Text.Bencode is a library for Bencode (BEP 3), the compact binary encoding used by BitTorrent for .torrent metadata and tracker responses. It is one of the three Bodu serializers and shares the architecture described in the family introduction (the serializer / DOM / reader-writer tiers, converters, attributes, naming policies) member-for-member with its siblings Bodu.Text.Toml and Bodu.Text.Yaml. This page covers what is specific to Bencode.
The format in one paragraph
Bencode has exactly four kinds: byte strings (4:spam), integers (i42e), lists (l…e), and dictionaries (d…e) whose keys are byte strings in ascending bytewise order. There is no Boolean, no floating-point, no date-time, and no null. Documents are binary, self-framing, and - when the BEP 3 canonical rules are followed - byte-for-byte deterministic for the same data, which is why torrent info-hashes can be computed over the encoded form.
Byte strings, not text
The native Bencode scalar is a byte string, not a character string. BencodeSerializer therefore treats binary data as a first-class citizen:
byte[],Memory<byte>, andReadOnlyMemory<byte>map directly to byte strings with no transcoding or Base64 detour.stringvalues are written as UTF-8 byte strings.enumvalues are written as member-name byte strings.
This makes Bencode a natural fit for payloads that mix identifiers with raw hashes or binary blobs - the torrent pieces field being the canonical example.
Canonical output
The writer always emits canonical BEP 3: dictionary entries appear in ascending bytewise key order regardless of member declaration order, integers carry no leading or negative zeros, and a null member is omitted. The reader is equally strict by default - it accepts only canonical input (unique ascending keys, a single root, no trailing bytes), so a successful round trip is byte-identical. Two opt-in switches relax the read path alone for documents from older, looser encoders - AllowUnsortedKeys and AllowDuplicateKeys on BencodeSerializerOptions - while the writer stays unconditionally canonical. The library's conformance to BEP 3 and the BEP 52 canonical-form clarifications is pinned by data-driven tests in Bodu.Text.Bencode/test (integer grammar, byte-string grammar, dictionary key rules, container balance, single-root documents); the engineering review behind them lives in the repository under docs/reviews/ and is not part of the published site.
What Bencode cannot represent
Because the format has only four kinds, several everyday .NET types have no native Bencode form. The serializer refuses to guess: serializing such a member fails with NotSupportedException ("No converter is configured for type '…'") unless a converter supplies a representation.
| .NET kind | Native form | Bridge |
|---|---|---|
bool |
none | a custom converter - e.g. map to i0e / i1e |
double / float / decimal |
none | a custom converter - e.g. a scaled integer or a byte string |
DateTime / DateTimeOffset / DateOnly / TimeOnly |
none | a custom converter - e.g. Unix seconds as an integer |
null values |
none | omitted on write by design |
The library never invents a lossy representation on your behalf; the choice of encoding for these kinds is yours, made explicit through a BencodeConverter<T>.
Headline types
| Type | Purpose |
|---|---|
| BencodeSerializer | Serialize / Deserialize<T> over byte[], ReadOnlySpan<byte>, IBufferWriter<byte>, and Stream (with async variants), plus the DOM bridges SerializeToNode / SerializeToDocument and Deserialize<T>(BencodeNode). |
| BencodeSerializerOptions | Converters, naming policy, case-insensitive matching, ignore conditions, unmapped-member and object-creation policy, read-path leniency (AllowUnsortedKeys / AllowDuplicateKeys), depth. |
| NamingPolicy | Property-name policy: CamelCase, SnakeCaseLower, SnakeCaseUpper, KebabCaseLower, KebabCaseUpper. |
| BencodeConverter<T> | Base class for a custom converter over the reader/writer pair; attach one to a member or type with ConverterAttribute. Built-in enum converters: BencodeStringEnumConverter and BencodeNumberEnumConverter<TEnum>. |
| BencodeNode | Mutable DOM - Parse, index, mutate, write back. |
| BencodeDocument | Read-only, low-allocation DOM walked through RootElement. |
| Utf8BencodeReader / Utf8BencodeWriter | Forward-only, allocation-free ref struct token machines. |
| BencodeFormatException / BencodeSerializationException | Malformed input vs a value that cannot bind. |
Common scenarios
| You want to… | Use |
|---|---|
| Encode or decode torrent-style metadata | BencodeSerializer.Serialize / Deserialize<T> |
Inspect a .torrent file without a model |
BencodeDocument and RootElement |
| Edit one dictionary entry and write the document back | BencodeNode |
| Carry raw hashes alongside text fields | byte[] members - they map straight to byte strings |
| Produce deterministic bytes for hashing or signing | the canonical writer - output order is independent of member order |
Represent a bool or timestamp on the wire |
a custom converter |
Where to go next
- Bodu serializers introduction - the shared shape: tiers, converters, attributes, callbacks, naming policies.
- Core concepts - the Bencode vocabulary, including the full Bencode value-mapping table.
- Getting started - install and the first round trip.
- Using Bencode - worked patterns: type mapping, converters for unrepresentable kinds, both DOMs, raw tokens.
- API reference - Bodu.Text.Bencode.