tpkt

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 13, 2026 License: MIT Imports: 4 Imported by: 2

README

go-tpkt

Go License Go Report Card CI Codecov Release

go-tpkt is a small, idiomatic Go library that implements the TPKT packet framing defined in RFC 1006.

TPKT is a simple header + payload packet format used to carry ISO transport protocol data units (TPDUs) over a TCP byte stream. This package focuses only on TPKT framing and validation; it does not interpret or implement any higher-level transport or application protocols.

Scope
  • In scope:

    • TPKT header construction and parsing
    • Encode/decode helpers for complete packets
    • Streaming Reader and Writer over io.Reader / io.Writer
    • Strict validation of version, reserved byte, and length fields (RFC 1006 packet length min=7, max=65535)
    • Protection against oversized frames
  • Out of scope:

    • COTP / CR/CC/DT TPDU parsing
    • TSAP addressing or ISO session/presentation
    • S7comm, MMS, IEC 61850, or any application protocol logic
    • TCP listener/server management

go-tpkt is intended as a foundation for higher-level stacks such as COTP, S7comm, or MMS over RFC 1006.

Install
go get github.com/otfabric/go-tpkt

Requires Go 1.22 or newer.

Basic usage
Encode and decode a single packet
package main

import (
	"log"

	"github.com/otfabric/go-tpkt"
)

func main() {
	payload := []byte{0x02, 0xf0, 0x80}

	pkt, err := tpkt.Encode(payload)
	if err != nil {
		log.Fatalf("encode: %v", err)
	}

	decoded, err := tpkt.Decode(pkt)
	if err != nil {
		log.Fatalf("decode: %v", err)
	}

	_ = decoded // use TPDU bytes in a higher-level protocol
}
Streaming reader
// conn is a net.Conn established to a peer speaking RFC 1006 TPKT.
// r := tpkt.NewReader(conn)

// payload, err := r.ReadFrame()
// if err != nil {
//     // handle EOF, malformed frames, etc.
// }
// // payload now contains a complete TPDU as bytes.
Streaming writer
// conn is a net.Conn
// w := tpkt.NewWriter(conn)
// _, err := w.WriteFrame(payload)
// if err != nil {
//     // handle write or framing error
// }
Relation to higher-level protocols

This package intentionally stops at TPKT framing. Protocols such as COTP, S7comm, and MMS can be implemented on top of the payloads read and written through this library without any coupling to their semantics.

Size limits, validation, and ownership
  • The library enforces the RFC 1006 minimum packet size of 7 octets (4-byte TPKT header + 3-byte minimum TPDU), so payloads must be at least 3 bytes long to be encodable.
  • tpkt.MinPacketLength and tpkt.MaxPacketLength expose the protocol bounds (7 and 65535) for callers that want to size buffers or apply their own checks.
  • Encode, Decode, and Parse validate packets against protocol structure only (version, reserved, length, buffer consistency); they do not apply any additional caller-configurable maximum frame size.
  • Reader applies the same structural checks and also enforces a configurable maximum total packet size via WithMaxFrameSize, clamping values below tpkt.MinPacketLength up to that minimum.
  • Decode and Parse return payload slices that alias the input buffer; if you need to retain or mutate them independently, copy the data first.
  • Reader.ReadFrame returns a newly allocated payload slice that is safe to mutate without affecting the underlying stream buffer.
  • Reader and Writer are not safe for concurrent use from multiple goroutines without external synchronization.

Documentation

Overview

Package tpkt implements TPKT framing as defined by RFC 1006.

TPKT is a simple header + payload packetization scheme used to carry ISO transport protocol data units (TPDUs) over a TCP byte stream. Each packet starts with a 4-byte header:

  • byte 0: version (always 3 for this version of the protocol)
  • byte 1: reserved (always 0)
  • bytes 2–3: total packet length in octets, big-endian, including header

This package is intentionally limited to TPKT framing only. It does not interpret or construct TPDUs and it does not implement:

  • COTP or CR/CC/DT TPDU semantics
  • TSAP addressing or ISO session/presentation
  • S7comm, MMS, or other application protocols

Callers are expected to build higher-level protocol stacks on top of the payload bytes returned by Decode, Parse, and Reader.ReadFrame, and to send payload bytes using Writer.WriteFrame.

The implementation follows RFC 1006 section 6 (Packet Format) and enforces:

  • version == 3
  • reserved == 0
  • total length field within the legal range [7, 65535]
  • consistency between the declared length and the actual buffer size

A total length of 7 bytes corresponds to a 4-byte TPKT header plus a 3-byte minimum TPDU for transport class 0. Frames declaring a length smaller than 7 (including a 4-byte header with no TPDU bytes) are rejected.

Validation and payload ownership:

  • Decode and Parse validate packets against protocol structure only (version, reserved, length, and buffer consistency). They do not apply any additional caller-configurable maximum frame size.
  • Reader.ReadFrame applies the same structural checks and also enforces a configurable maximum total packet size before allocating payload memory.
  • Decode and Parse: the returned payload aliases the input buffer.
  • Reader.ReadFrame: the returned slice is a newly allocated payload.

Reader and Writer are not safe for concurrent use from multiple goroutines without external synchronization.

Index

Examples

Constants

View Source
const (
	MinPacketLength = rfcMinPacketLength
	MaxPacketLength = rfcMaxPacketLength
)

Exported protocol size bounds for callers that want to configure buffers or validation in terms of on-the-wire TPKT sizes.

View Source
const HeaderLength = 4

HeaderLength is the length in octets of the fixed TPKT header.

View Source
const Version byte = 3

Version is the TPKT protocol version supported by this package. RFC 1006 defines this value as 3.

Variables

View Source
var (
	// ErrTooShort indicates that a buffer is shorter than the 4-byte TPKT header.
	ErrTooShort = errors.New("tpkt: packet too short")
	// ErrInvalidVersion indicates a header with a version byte other than 3.
	ErrInvalidVersion = errors.New("tpkt: invalid version")
	// ErrInvalidReserved indicates a header with a non-zero reserved byte.
	ErrInvalidReserved = errors.New("tpkt: invalid reserved byte")
	// ErrInvalidLength indicates that the declared TPKT length is outside
	// the legal range or otherwise structurally invalid.
	ErrInvalidLength = errors.New("tpkt: invalid length field")
	// ErrLengthMismatch indicates that the declared TPKT length does not
	// match the actual size of the buffer.
	ErrLengthMismatch = errors.New("tpkt: length field does not match buffer size")
	// ErrFrameTooLarge indicates that a frame exceeds a configured or
	// protocol-defined maximum size.
	ErrFrameTooLarge = errors.New("tpkt: frame exceeds maximum allowed size")
)

Exported sentinel errors. Callers should use errors.Is to classify failures.

Functions

func Decode

func Decode(pkt []byte) ([]byte, error)

Decode validates a complete TPKT packet and returns only the payload.

The returned slice aliases pkt; callers must copy it if they need to retain it independently.

Example
// A minimal valid TPKT packet: version=3, reserved=0, length=7, 3-byte payload.
pkt := []byte{0x03, 0x00, 0x00, 0x07, 0x01, 0x02, 0x03}

decoded, err := tpkt.Decode(pkt)
if err != nil {
	panic(err)
}

fmt.Println(decoded)
Output:
[1 2 3]

func Encode

func Encode(payload []byte) ([]byte, error)

Encode builds a complete TPKT packet from the provided payload.

The returned slice contains the 4-byte TPKT header followed by the payload. The header is constructed according to RFC 1006 section 6.

Example
payload := []byte{0x02, 0xf0, 0x80}

pkt, err := tpkt.Encode(payload)
if err != nil {
	panic(err)
}

// Inspect header fields of the encoded packet.
version := pkt[0]
reserved := pkt[1]
length := int(binary.BigEndian.Uint16(pkt[2:4]))

fmt.Println(version, reserved, length == len(pkt))
Output:
3 0 true

Types

type Frame

type Frame struct {
	Payload []byte
}

Frame represents a single TPKT-framed TPDU.

The Payload is treated as an opaque sequence of bytes by this package; higher level protocols (e.g. COTP, S7, MMS) are expected to interpret it. A Frame value by itself does not guarantee RFC 1006 validity: payloads that are too short or too large will be rejected when marshaled or encoded.

func Parse

func Parse(pkt []byte) (Frame, error)

Parse validates a complete TPKT packet and returns a Frame.

The returned Frame.Payload aliases pkt; callers must copy it if they need to retain it independently.

func (Frame) Len

func (f Frame) Len() int

Len reports HeaderLength + len(Payload), i.e. the total length the frame would occupy on the wire if encodable.

func (Frame) MarshalBinary

func (f Frame) MarshalBinary() ([]byte, error)

MarshalBinary encodes the Frame into a complete TPKT packet.

The returned slice is a newly allocated buffer. If the encoded packet would be smaller than MinPacketLength or larger than MaxPacketLength, an error is returned.

type Reader

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

Reader reads TPKT-framed payloads from an underlying io.Reader.

It is safe for use with any streaming source such as a net.Conn. Reader is not safe for concurrent use from multiple goroutines without external synchronization.

func NewReader

func NewReader(r io.Reader, opts ...ReaderOption) *Reader

NewReader constructs a Reader over r.

By default it accepts packets up to the RFC 1006 maximum. WithMaxFrameSize can be used to impose a stricter bound.

func (*Reader) ReadFrame

func (r *Reader) ReadFrame() ([]byte, error)

ReadFrame reads the next TPKT frame from the stream and returns its payload.

It returns io.EOF when called at a frame boundary and the underlying reader has no more data. If the stream ends in the middle of a header or payload, it returns an error wrapping io.ErrUnexpectedEOF.

Example
payload := []byte{0x01, 0x02, 0x03}
pkt, _ := tpkt.Encode(payload)

r := tpkt.NewReader(bytes.NewReader(pkt))

got, err := r.ReadFrame()
if err != nil {
	panic(err)
}

fmt.Println(bytes.Equal(got, payload))
Output:
true

type ReaderOption

type ReaderOption func(*Reader)

ReaderOption configures a Reader.

func WithMaxFrameSize

func WithMaxFrameSize(n int) ReaderOption

WithMaxFrameSize sets an upper bound on the total TPKT packet size (header plus payload) that the Reader will accept.

Values less than or equal to zero leave the default in place. Values greater than zero but smaller than MinPacketLength are clamped up to MinPacketLength.

type Writer

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

Writer writes payloads as TPKT-framed packets to an underlying io.Writer. It currently exposes only the WriteFrame helper; callers that wish to send a Frame should pass f.Payload explicitly. Writer is not safe for concurrent use from multiple goroutines without external synchronization.

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter constructs a Writer over w.

func (*Writer) WriteFrame

func (w *Writer) WriteFrame(payload []byte) (int, error)

WriteFrame encodes payload as a TPKT packet and writes it in full.

It returns the total number of octets written (header plus payload). If the underlying writer performs a short write or returns an error, WriteFrame returns a non-nil error.

Example
var buf bytes.Buffer
w := tpkt.NewWriter(&buf)

payload := []byte{0x01, 0x02, 0x03}
if _, err := w.WriteFrame(payload); err != nil {
	panic(err)
}

expected, _ := tpkt.Encode(payload)
ok := bytes.Equal(buf.Bytes(), expected)
fmt.Println(ok)
Output:
true

Jump to

Keyboard shortcuts

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