How Torrent Parsers Handle Dictionaries and Bytes
A .torrent file uses a compact serialization format
called Bencode to store metadata about shared files and trackers. This
article explains the technical mechanics of how a Bencode parser
processes raw byte strings and nested dictionary structures. It covers
the parsing algorithms for length-prefixed binary data, the use of
recursion and state machines for nested structures, and the critical
need to preserve raw byte boundaries for cryptographic hash
verification.
Understanding the Bencode Format
Before examining dictionaries and byte strings, it is necessary to understand the four primary data types defined by the BitTorrent specification:
- Integers: Wrapped in
iande(e.g.,i42e). - Byte Strings: Formatted as
<length>:<data>(e.g.,4:spam). - Lists: Wrapped in
lande(e.g.,l4:spami42ee). - Dictionaries: Wrapped in
dandecontaining alternating keys and values.
Because Bencode uses ASCII delimiters alongside raw binary payloads, a parser cannot simply decode the entire file as a standard UTF-8 string. It must process the input as an arbitrary array of bytes.
Parsing Byte Strings
Byte strings in a .torrent file are length-prefixed. The
parser identifies a byte string when it encounters an ASCII digit
(0 through 9).
The Parsing Algorithm:
- Read the Length: The parser reads ASCII digits
sequentially until it hits the delimiter character
:(ASCII0x3A). - Convert to Integer: The parsed ASCII characters are converted to an integer, representing the byte count \(N\).
- Extract Payload: The parser advances its cursor past the colon and reads exactly \(N\) consecutive bytes directly from the byte stream into a byte buffer.
Raw Data: 5 : h e l l o
Cursor: [0] ^ (Length = 5)
[1] ^ (Delimiter)
[2..6] ^ (Read 5 raw bytes: "hello")
Handling Binary vs. Text Data
Not all byte strings represent human-readable text. For example, the
pieces field inside the info dictionary
contains concatenated 20-byte SHA-1 hashes of each file chunk. A correct
parser must store string values as raw byte arrays rather than
automatically decoding them into text, preventing encoding errors or
corrupted hashes when encountering arbitrary binary data.
Parsing Nested Dictionaries
Dictionaries start with the byte d (ASCII
0x64) and terminate with the byte e (ASCII
0x65). They consist of alternating key-value pairs where: -
Keys must be Bencoded byte strings. - Keys
must appear in lexicographical (binary) order. - Values
can be any valid Bencode data type, including another dictionary or
list.
Handling Nesting with Recursive Descent
When a parser encounters a d marker, it enters a
dictionary-parsing state:
- The parser initializes an empty map or associative array.
- It loops until it encounters the terminator byte
e. - In each iteration:
- It parses the next element, ensuring it is a byte string (the dictionary key).
- It parses the following element as the value. If the value starts
with
d, the parser makes a recursive call to the dictionary parser. - The key-value pair is stored in the current map.
- When the matching
eis reached, the parser returns the populated dictionary and advances past the terminator.
Raw Stream: d 4:info d 4:name 4:file e e
Structure: {
"info": {
"name": "file"
}
}
Iterative Parsing Using an Explicit Stack
In environments where deep recursion might cause stack overflow
issues, parsers use an explicit stack to maintain state: - When a
d is found, a new dictionary container is pushed onto the
stack. - The parser tracks whether it expects a key or a value for the
container at the top of the stack. - When an e is
encountered, the container at the top of the stack is popped and
attached to the parent structure beneath it.
Preserving Raw Offsets for the Info Hash
A unique requirement when parsing .torrent dictionaries
is calculating the info_hash. The BitTorrent protocol
identifies torrents by the SHA-1 hash of the bencoded info
dictionary.
Because dictionary key ordering, whitespace, and integer
representations can vary if re-encoded naively by a library, standard
parsers must record the exact start and end byte offsets of the
info dictionary within the raw file. The parser slices
these raw bytes directly from the source buffer and passes them through
a SHA-1 hashing function, guaranteeing an accurate
info_hash matching the torrent network.