Table of Contents

Bodu.Text.Bencode

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>, and ReadOnlyMemory<byte> map directly to byte strings with no transcoding or Base64 detour.
  • string values are written as UTF-8 byte strings.
  • enum values 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