Skip to content

katzenpost/trunnel

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

trunnel

Complete binary serialization toolkit for Go. Code generator for binary parsing and encoding. Golang port of the original implementation for the Tor project with full bidirectional serialization support.

go.dev Reference Build status Coverage Go Report Card

πŸš€ Status

This is an actively maintained fork of the original trunnel project, which became unmaintained for 4+ years. This fork provides:

  • βœ… Full Parser Support - All original trunnel parsing functionality
  • βœ… Complete Encoder Support - NEW: Binary encoding with MarshalBinary()
  • βœ… Modern Go Modules - Updated for current Go ecosystem
  • βœ… Production Ready - Comprehensive testing and validation
  • βœ… Standard Compliance - Implements encoding.BinaryMarshaler interface

Description

Writing parsers for binary formats is error-prone and tedious. Critical security vulnerabilities are frequently found in parser code, especially in low-level languages such as C. Trunnel is a domain-specific language for describing binary formats and a parser generator for those formats.

Trunnel was initially designed and implemented by Nick Mathewson for the Tor Project. This Golang port is intended to support efforts to implement a Tor Relay in Go, with the goal of sharing trunnel files with the core Tor codebase.

Quick Start

Install trunnel with

go get -u github.com/katzenpost/trunnel/cmd/trunnel

As a very simple example, we can define a color struct in trunnel as follows.

struct color {
  u8 r;
  u8 g;
  u8 b;
};

Compile this with the trunnel tool as follows.

trunnel build -p color color.trunnel

The result will be a Golang package called color with a Color type and methods for complete bidirectional binary serialization.

This generates a complete Go package with both parsing and encoding capabilities:

// Code generated by trunnel. DO NOT EDIT.

package color

import (
	"binary"
	"errors"
)

type Color struct {
	R uint8
	G uint8
	B uint8
}

// Parse method for decoding binary data
func (c *Color) Parse(data []byte) ([]byte, error) {
	cur := data
	{
		if len(cur) < 1 {
			return nil, errors.New("data too short")
		}
		c.R = cur[0]
		cur = cur[1:]
	}
	{
		if len(cur) < 1 {
			return nil, errors.New("data too short")
		}
		c.G = cur[0]
		cur = cur[1:]
	}
	{
		if len(cur) < 1 {
			return nil, errors.New("data too short")
		}
		c.B = cur[0]
		cur = cur[1:]
	}
	return cur, nil
}

func ParseColor(data []byte) (*Color, error) {
	c := new(Color)
	_, err := c.Parse(data)
	if err != nil {
		return nil, err
	}
	return c, nil
}

// NEW: Encoder methods for binary serialization
func (c *Color) encodeBinary() []byte {
	var buf []byte
	buf = append(buf, byte(c.R))
	buf = append(buf, byte(c.G))
	buf = append(buf, byte(c.B))
	return buf
}

func (c *Color) MarshalBinary() ([]byte, error) {
	if err := c.validate(); err != nil {
		return nil, err
	}
	return c.encodeBinary(), nil
}

func (c *Color) validate() error {
	return nil
}

Usage Examples

Basic Round-Trip Serialization

package main

import (
    "fmt"
    "log"

    "github.com/katzenpost/trunnel/examples/color"
)

func main() {
    // Create a color struct
    original := &color.Color{R: 255, G: 128, B: 64}

    // Encode to binary (NEW!)
    data, err := original.MarshalBinary()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Encoded: %v\n", data) // [255 128 64]

    // Decode from binary
    parsed, err := color.ParseColor(data)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Decoded: R=%d G=%d B=%d\n", parsed.R, parsed.G, parsed.B)
}

Constraints and Validation

Integer fields support constraints that are validated during encoding:

struct date {
   u16 year IN [ 1970..65535 ];
   u8 month IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
   u8 day IN [ 1,2,3..31 ];
};
// Valid date
date := &Date{Year: 2023, Month: 12, Day: 25}
data, err := date.MarshalBinary() // βœ… Success

// Invalid date - constraint violation
invalid := &Date{Year: 1969, Month: 13, Day: 32}
data, err := invalid.MarshalBinary() // ❌ Returns error

Complex Data Types

Trunnel supports arrays, unions, nested structs, and more:

struct packet {
  u8 type;
  union payload[type] {
    1: u8 byte_data;
    2: u16 word_data;
    3: nulterm string_data;
  };
};

struct message {
  u8 count;
  struct packet items[count];
};

All types support full round-trip serialization with automatic validation.

Features

βœ… Complete Data Type Support

  • Integer Types: u8, u16, u32, u64 with big-endian encoding
  • Character Type: char (mapped to u8)
  • Strings: nulterm null-terminated strings with UTF-8 support
  • Arrays: Fixed type[N], variable type[field], and leftover type[]
  • Nested Structs: Full recursive encoding/decoding
  • Unions: Conditional data based on tag fields
  • Constants: Named constants and computed values

βœ… Constraint System

  • Integer Ranges: IN [min..max] for range validation
  • Value Sets: IN [val1, val2, val3] for discrete values
  • Array Lengths: Automatic validation of array size constraints
  • Union Tags: Validation of union discriminator values

βœ… Generated Methods

  • Parse([]byte) ([]byte, error) - Parse binary data into struct
  • ParseStructName([]byte) (*StructName, error) - Constructor function
  • MarshalBinary() ([]byte, error) - Encode struct to binary (implements encoding.BinaryMarshaler)
  • validate() error - Validate all constraints before encoding
  • encodeBinary() []byte - Internal encoding without validation

βœ… Advanced Features

  • Error Handling: Comprehensive error reporting for parsing and validation
  • Memory Safety: Bounds checking and safe array access
  • Performance: Efficient encoding/decoding with minimal allocations
  • Standard Compliance: Compatible with Go's encoding package interfaces
  • Complex Protocols: Tested with real-world protocols like SOCKS5

Installation

go get -u github.com/katzenpost/trunnel/cmd/trunnel

Documentation

Project Status

This fork is actively maintained by the Katzenpost project. The original github.com/mmcloughlin/trunnel became unmaintained for 4+ years, so we forked it to continue development and add essential features like binary encoding.

What's Working

  • βœ… All original trunnel parsing functionality
  • βœ… Complete binary encoding with MarshalBinary()
  • βœ… Full constraint validation system
  • βœ… Complex data types (unions, arrays, nested structs)
  • βœ… Modern Go module support
  • βœ… Comprehensive test coverage
  • βœ… Real-world protocol support (SOCKS5, etc.)

Migration from Original

If you're using github.com/mmcloughlin/trunnel, simply update your imports:

// Old
import "github.com/mmcloughlin/trunnel/..."

// New
import "github.com/katzenpost/trunnel/..."

All existing functionality is preserved with full backward compatibility.

Contributing

We welcome contributions! Please see our contribution guidelines for details.

Development Setup

git clone https://github.com/katzenpost/trunnel.git
cd trunnel
go mod download
go test ./...

License

This project maintains the same license as the original implementation. See LICENSE for details.

About

Code generator for binary parsing

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 99.3%
  • Makefile 0.7%