bencode

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 4 Imported by: 1

README

go-bencode

GitHub tag (latest SemVer) Go Reference

Decoding and encoding bencode.

Support All go type including map/slice/struct/array, and simple type like bool/int/uint/string/....

float32 and float64 are not supported, bencode doesn't have this type.

Encoding and decoding some type from standard library like time.Time, net.IP are not supported. If you have any thought about how to support these types, please create an issue.

Or you can wrap these types and implement bencode.Marshaler or bencode.Unmarshaler

Install

go get github.com/trim21/go-bencode

Usage

See examples

Marshal

Bencode doesn't have null type, so all pointer fields (*T) in structs get omitempty by default — a nil pointer is skipped during encoding.

If you want to apply omitempty to custom types, implement both bencode.Marshaler and bencode.IsZeroValue, so the encoder can determine whether a value is empty.

See bencode.RawBytes for an example.

Unmarshal
Basic Types
var b bool
bencode.Unmarshal([]byte("i1e"), &b) // true
bencode.Unmarshal([]byte("i0e"), &b) // false

var i int
bencode.Unmarshal([]byte("i100e"), &i)  // 100
bencode.Unmarshal([]byte("i-100e"), &i) // -100

var u uint
bencode.Unmarshal([]byte("i100e"), &u)  // 100

var s string
bencode.Unmarshal([]byte("5:hello"), &s) // "hello"
bencode.Unmarshal([]byte("0:"), &s)      // ""

Supported integer types: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *big.Int.

Raw Bytes

[]byte and [N]byte are decoded as bencode string (raw bytes, not list of integers):

var b []byte
bencode.Unmarshal([]byte("5:hello"), &b) // []byte("hello")

var arr [20]byte
bencode.Unmarshal([]byte("20:aaaaaaaaaaaaaaaaaaaa"), &arr)

Go arrays have a strict length check: the bencode string/list must have exactly the same length, otherwise an error is returned.

any Type

When decoding into any, the target type is inferred:

var v any
bencode.Unmarshal([]byte("i1e"), &v)                          // int64(1)
bencode.Unmarshal([]byte("5:hello"), &v)                      // "hello"
bencode.Unmarshal([]byte("le"), &v)                           // []any{}
bencode.Unmarshal([]byte("li1ei2ei3ee"), &v)                  // []any{int64(1), int64(2), int64(3)}
bencode.Unmarshal([]byte("de"), &v)                           // map[string]any{}
bencode.Unmarshal([]byte("d1:ai1e1:bssee"), &v)               // map[string]any{"a": int64(1), "b": "ss"}
Structs

Struct fields are mapped by the bencode tag. Use - to skip a field, or bencode:",omitempty" to omit zero values.

type Container struct {
    F string `bencode:"f1q"`
    V int64  `bencode:"1a9"`
    Skip bool `bencode:"-"`
}

var c Container
bencode.Unmarshal([]byte(`d3:f1q10:0147852369e`), &c)
// c.F == "0147852369"

Missing fields are left at their zero values; extra unknown keys are skipped silently.

Pointer fields are set to nil when the key is absent, and allocated when present:

type Container struct {
    F *string `bencode:"f1q"`
}
var c Container
bencode.Unmarshal([]byte(`d3:f1q10:0147852369e`), &c) // c.F != nil, *c.F == "0147852369"
bencode.Unmarshal([]byte(`de`), &c)                    // c.F == nil

Nested pointer (**T) is not supported.

Anonymous (embedded) struct fields are flattened into the parent. If an anonymous field has its own bencode tag, it is treated as a named sub-dict instead.

Slices & Maps
// slices
type S struct {
    Value []string `bencode:"value"`
}
var c S
bencode.Unmarshal([]byte(`d5:valuel3:one3:two1:qee`), &c)
// c.Value == []string{"one", "two", "q"}

// maps (keys must be string or [N]byte)
type M struct {
    Value map[string]string `bencode:"value"`
}
var c M
bencode.Unmarshal([]byte(`d5:valued4:five1:54:four1:4ee`), &c)
// c.Value == map[string]string{"five": "5", "four": "4"}

Map keys can also be [N]byte (fixed-size byte array).

Custom Unmarshaler

Implement bencode.Unmarshaler for custom decoding logic:

type Unmarshaler interface {
    UnmarshalBencode([]byte) error
}

bencode.RawBytes is a built-in type that implements this interface, capturing raw bencode bytes:

var c struct {
    Value bencode.RawBytes `bencode:"value"`
}
bencode.Unmarshal([]byte(`d5:valued3:keyli1ei2eee`), &c)
// string(c.Value) == `d3:keyli1ei2eee`
Strict vs Relaxed Parsing

By default, Unmarshal enforces strict bencode rules:

  • Dictionary keys must be in lexicographic order
  • Duplicate dictionary keys are rejected

Use UnmarshalRelaxed to allow out-of-order keys and duplicate keys (last value wins):

// strict: fails because keys are out of order
bencode.Unmarshal([]byte(`d1:bi2e1:ai1ee`), &m)

// relaxed: succeeds
bencode.UnmarshalRelaxed([]byte(`d1:bi2e1:ai1ee`), &m)
// m == map[string]int64{"a": 1, "b": 2}

// strict: fails on duplicate keys
bencode.Unmarshal([]byte(`d1:ai1e1:ai2ee`), &m)

// relaxed: last value wins
bencode.UnmarshalRelaxed([]byte(`d1:ai1e1:ai2ee`), &m)
// m == map[string]int64{"a": 2}
Notes
  • Input must not be empty (""), regardless of target type.
  • Decoded Go string may not be valid UTF-8; validate yourself if needed.
  • Go arrays (not slices) are decoded with a length check — the bencode list/string must have exactly the same number of elements.

Note

go reflect package allow you to create dynamic struct with reflect.StructOf, but please use it with caution.

For performance, this package will try to "compile" input type to a static encoder/decoder at first time and cache it for future use.

So a dynamic struct may cause memory leak.

License

MIT License

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AppendBytes added in v0.0.13

func AppendBytes(b []byte, s []byte) []byte

func AppendInt added in v0.0.13

func AppendInt(b []byte, i int64) []byte

func AppendStr added in v0.0.13

func AppendStr(b []byte, s string) []byte

func Marshal

func Marshal(v any) ([]byte, error)
Example
package main

import (
	"fmt"

	"github.com/trim21/go-bencode"
)

func main() {
	type User struct {
		ID   uint32 `bencode:"id,string"`
		Name string `bencode:"name"`
	}

	type Inner struct {
		V int    `bencode:"v"`
		S string `bencode:"a long string name replace field name"`
	}

	type With struct {
		Users   []User `bencode:"users,omitempty"`
		Obj     Inner  `bencode:"obj"`
		Ignored bool   `bencode:"-"`
	}

	var data = With{
		Users: []User{
			{ID: 1, Name: "sai"},
			{ID: 2, Name: "trim21"},
		},
		Obj: Inner{V: 2, S: "vvv"},
	}
	var b, err = bencode.Marshal(data)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(b))
}
Output:
d3:objd37:a long string name replace field name3:vvv1:vi2ee5:usersld2:idi1e4:name3:saied2:idi2e4:name6:trim21eee

func Unmarshal

func Unmarshal(data []byte, v any) error
Example
package main

import (
	"fmt"

	"github.com/trim21/go-bencode"
)

func main() {
	var v struct {
		S     map[[20]byte]struct{} `bencode:"s"`
		Value map[string]string     `bencode:"value" json:"value"`
	}
	raw := `d5:valued4:five1:5ee`

	err := bencode.Unmarshal([]byte(raw), &v)
	if err != nil {
		panic(err)
	}

	fmt.Println(v.Value["five"])
}
Output:
5

func UnmarshalRelaxed added in v0.1.0

func UnmarshalRelaxed(data []byte, v any) error

UnmarshalRelaxed is like Unmarshal but with relaxed parsing rules: - Dictionary keys are not required to be sorted - Duplicate dictionary keys are allowed (last value wins)

Types

type Encoder added in v0.0.8

type Encoder struct {
	// contains filtered or unexported fields
}

func NewEncoder added in v0.0.9

func NewEncoder(w io.Writer) *Encoder

func (*Encoder) Encode added in v0.0.8

func (e *Encoder) Encode(v any) error

type IsZeroValue added in v0.0.4

type IsZeroValue interface {
	// IsZeroBencodeValue enable support for omitempty feature.
	// if it's being used as struct field, and it returns true, this field will be skipped.
	IsZeroBencodeValue() bool
}

IsZeroValue add support type implements Marshaler and omitempty

var s struct {
	Field T `bencode:"field,omitempty"`
}

if `T` implements Marshaler, it can implement IsZeroValue so bencode know if it's

type Marshaler

type Marshaler interface {
	MarshalBencode() ([]byte, error)
}

Marshaler allow users to implement its own encoder.

type RawBytes added in v0.0.3

type RawBytes []byte

func (RawBytes) IsZeroBencodeValue added in v0.0.4

func (b RawBytes) IsZeroBencodeValue() bool

func (RawBytes) MarshalBencode added in v0.0.3

func (b RawBytes) MarshalBencode() ([]byte, error)

func (*RawBytes) UnmarshalBencode added in v0.0.3

func (b *RawBytes) UnmarshalBencode(bytes []byte) error

type Unmarshaler

type Unmarshaler interface {
	UnmarshalBencode([]byte) error
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL