Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/bencode/v0

Directory · 4 Files
README.md Open

gno.land/p/moul/x/daily/bencode/v0

Bencoding, the BitTorrent serialization format: Decode, Value.Encode, NewInt, NewString, NewList, NewDict.

 1import "gno.land/p/moul/x/daily/bencode/v0"
 2
 3v, err := bencode.Decode("d3:cow3:moo4:spam4:eggse")
 4v.Get("cow").Text()   // "moo"
 5v.Keys()              // ["cow", "spam"]
 6v.Encode()            // "d3:cow3:moo4:spam4:eggse", byte for byte
 7
 8d := bencode.NewDict()
 9d.Set("spam", bencode.NewString("eggs"))
10d.Set("cow", bencode.NewString("moo"))
11d.Encode()            // "d3:cow3:moo4:spam4:eggse": insertion order does not matter

Four types and no escape syntax. Integers (i42e), byte strings (5:hello), lists (l...e) and dictionaries (d...e). No float, no boolean, no null. A string carries its length instead of quoting rules, so a value can contain any byte, including the delimiters.

The point is canonicality, and that is why the decoder is strict. One value has exactly one encoding: dictionary keys sorted bytewise, no leading zeros, no negative zero, no trailing bytes. A BitTorrent infohash is the digest of a re-encoded info dictionary, so a decoder that accepts a sloppy encoding and re-emits a tidy one silently changes the identity of what it parsed. This one rejects non-canonical input rather than normalizing it, which makes the round trip a guarantee: for every input Decode accepts, Encode returns the original bytes. TestCanonicalIsAFixedPoint pins that property.

ErrNotCanonical is kept apart from ErrSyntax on purpose: "this parses but no encoder would emit it" is a different message to show a user than "this is not bencode".

Nothing that encodes to nothing can be stored: Set and NewList ignore a nil and the zero Value, because either one would emit a key with no value, or a list whose Len disagrees with its encoding, and Decode would then reject what Encode produced.

Encode is total. It returns "" for anything with no canonical encoding, which means a value nested deeper than MaxDepth and therefore any cycle a caller assembled by hand. Set refuses the direct case (a dictionary holding itself) outright; two dictionaries holding each other cannot be caught at insert time without walking the whole structure, so the depth bound in Encode is what keeps them from aborting the call. "" is not a legal bencode document, so a caller can test for it. Set also never writes through the slices an earlier Set produced, so copying the struct behind the pointer cannot leave two values partially aliased.

Accessors never panic. Int, Text, Len, At and Get return the zero value for the wrong kind or a nil receiver, so walking a document of unknown shape cannot abort a realm call. Input is bounded by MaxLen (8192 bytes) and MaxDepth (16) so gas stays predictable.

Not implemented: streaming, raw []byte values distinct from strings, and integers wider than int64.

Live demo: r/moul/x/daily/bencodedemo · render it at /r/moul/x/daily/bencodedemo/v0.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.