tlv

package
v0.0.0-...-d87cace Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2025 License: BSD-3-Clause Imports: 8 Imported by: 0

Documentation

Overview

Package tlv implements streaming encoding and decoding of the tag-length-value (TLV) format used by the Basic Encoding Rules (BER) and related encoding rules as specified in Rec. ITU-T X.690. See also “A Layman's Guide to a Subset of ASN.1, BER, and DER”.

The Encoder and Decoder types are used to encode or decode a stream of TLV headers and their values. This package deals with the syntactic layer of TLV-encoding while other packages such as codello.dev/asn1/ber deal with the semantic layer of BER.

Headers and Values

In BER each value is encoded using a tag-length-value format. The tag and length (we call them a header) are represented by the Header type. Values can use the primitive or constructed encoding. Primitive values can be read and written via io.Reader and io.Writer interfaces. Values using the constructed encoding are followed by more BER-encoded values and can either end implicitly (when using definite-length encoding) or explicitly (indefinite length).

The end of a constructed data value is signalled by a zero Header (or, equivalently, using TagEndOfContents or EndOfContents). The Encoder and Decoder types expect and produce an end-of-contents marker at the end of every constructed encoding, regardless of whether it uses the definite or indefinite-length encoding.

The Encoder and Decoder types contain methods to read and write BER values as a stream of headers, values, and end-of-content markers. They maintain an internal state to validate whether the sequence of TLVs forms a valid BER encoding.

Index

Examples

Constants

View Source
const LengthIndefinite = -1

LengthIndefinite when used as a magic number for the length of a Header indicates that the data value is encoded using the constructed indefinite-length format.

View Source
const TagEndOfContents = asn1.TagReserved

TagEndOfContents is the tag that signifies the end of a constructed data value. You can use this constant for clarity, the following are the same:

tlv.Header{}
tlv.Header{Tag: tlv.TagEndOfContents}
tlv.EndOfContents

Variables

View Source
var EndOfContents = Header{Tag: TagEndOfContents}

EndOfContents is the end-of-contents marker signalling the end of a constructed data value. The following are equivalent:

tlv.Header{}
tlv.Header{Tag: tlv.TagEndOfContents}
tlv.EndOfContents

Functions

func CombinedLength

func CombinedLength(ls ...int) int

CombinedLength returns the length of a data value encoding (not including its header) consisting of data value encodings of the specified lengths. If any of the passed lengths are LengthIndefinite or the result does not fit into the int type, the result is LengthIndefinite.

Example
fmt.Println(CombinedLength(42, LengthIndefinite))
fmt.Println(CombinedLength(math.MaxInt, 2))
Output:

-1
-1

func HeaderSize

func HeaderSize(h Header) int

HeaderSize returns the minimum number of bytes required to encode h.

func MinLength

func MinLength(l1, l2 int) int

MinLength returns the smaller of the two given lengths. This function is aware of potentially indefinite lengths and treats them properly.

Example
fmt.Println(MinLength(42, LengthIndefinite))
Output:

42

Types

type Decoder

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

Decoder is a streaming decoder for the TLV format used by ASN.1 encoding rules such as BER, DER or CER. It is used to read a stream of top-level tag-length-value (TLV) constructs.

Decoder can be used in presence of transient errors from the underlying reader. If an error occurs, the decoder is - in effect - reset to the state before the last ReadHeader call.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder creates a new Decoder reading from r. If r does not implement io.ByteReader, Decoder will do its own buffering. The buffering mechanism of Decoder attempts to buffer at most the number of bytes that belong to the current top-level TLV. However, if a top-level TLV uses the indefinite length format, the Decoder may buffer past the end of the value.

func (*Decoder) DataValueOffset

func (d *Decoder) DataValueOffset() int64

DataValueOffset returns the input byte offset where the current data value starts. This is the first byte of the identifier octets of the current value.

func (*Decoder) InputOffset

func (d *Decoder) InputOffset() int64

InputOffset returns the current input byte offset. The number of bytes actually read from the underlying io.Reader may be more than this offset due to internal buffering effects.

  • If the current TLV uses the primitive encoding, it gives the number of bytes that have been read from the input, including any bytes read from the current value.
  • If the current TLV uses the constructed encoding, it gives the location of the first byte of the next TLV header in the input.

func (*Decoder) PeekHeader

func (d *Decoder) PeekHeader() (Header, error)

PeekHeader reads the next TLV header from the input without advancing d. You can consume the peeked header using the ReadHeader method.

PeekHeader shares the same semantics as ReadHeader. In particular at the end of constructed data values there is always an EndOfContents (even for definite-length data values) and transient errors from the underlying reader can be retried.

func (*Decoder) ReadHeader

func (d *Decoder) ReadHeader() (Header, io.ReadCloser, error)

ReadHeader reads the next TLV header from the input. At the end of constructed TLVs a Header with TagEndOfContents will be returned (for both definite and indefinite-length encodings). If an error occurs during decoding the TLV header, or it is detected that the TLV structure is invalid, an error is returned.

The second return value is non-nil iff the decoded Header indicates the use of the primitive encoding. The io.ReadCloser can be used to read the contents of the primitive TLV. It also implements io.ByteReader. io.Closer.Close must be called before the next call of Decoder.ReadHeader or Decoder.PeekHeader.

ReadHeader can be used in presence of transient errors. If the underlying reader returns an error during the read operation, ReadHeader will return that error (potentially wrapped). If errors in the underlying reader are non-fatal, you can retry ReadHeader to resume the previous, erroneous call.

func (*Decoder) Reset

func (d *Decoder) Reset(r io.Reader)

Reset resets the state of d to read from r. See NewDecoder for details.

Reset reuses the internal buffer of d which may save some allocations compared to NewDecoder.

func (*Decoder) Skip

func (d *Decoder) Skip() (err error)

Skip discards the remainder of the current data value. If it uses the primitive encoding, only that value is discarded. If it is constructed, everything until the matching end-of-contents is skipped.

If at any point an error is encountered, the skipping will be stopped and the error returned.

func (*Decoder) StackDepth

func (d *Decoder) StackDepth() int

StackDepth returns the number of nested constructed TLVs of the current location of d. Each level represents a constructed TLV. It is incremented whenever a constructed TLV is encountered and decremented whenever a constructed TLV ends. The depth is zero-indexed, where zero represents the (virtual) top-level TLV.

func (*Decoder) StackIndex

func (d *Decoder) StackIndex(i int) Header

StackIndex returns information about the specified stack level. It must be a number between 0 and Decoder.StackDepth, inclusive.

The TLV header at level 0 represents the top level and is not present in the input data. The top-level TLV header is a constructed, indefinite-length data value with tag 0.

type Encoder

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

Encoder is a streaming encoder for the TLV format used by ASN.1 encoding rules such as BER, DER or CER. It is used to write a stream of top-level tag-length-value (TLV) constructs.

Encoder can be used in presence of transient errors from the underlying writer. If an error occurs, the encoder is - in effect - reset to the state before the last WriteHeader call.

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder creates a new Encoder writing to w. If w does not implement io.ByteWriter, Encoder will do its own buffering. The buffer is automatically flushed at the end of each top-level data value.

func (*Encoder) DataValueOffset

func (e *Encoder) DataValueOffset() int64

DataValueOffset returns the output byte offset where the current data value begins. This is the first byte of the identifier octets of the current data value.

func (*Encoder) OutputOffset

func (e *Encoder) OutputOffset() int64

OutputOffset returns the current output byte offset. It gives the location of the next byte immediately after the most recently written header or value. The number of bytes actually written to the underlying io.Writer may be less than this offset due to internal buffering effects.

func (*Encoder) Reset

func (e *Encoder) Reset(w io.Writer)

Reset resets the state of e to write to w. See NewEncoder for details.

Reset reuses the internal buffer of e which may save some allocations compared to NewEncoder.

func (*Encoder) StackDepth

func (e *Encoder) StackDepth() int

StackDepth returns the depth of nested constructed TLVs that have been opened and not closed by WriteHeader. Each level on the stack represents a constructed TLV. It is incremented whenever a constructed TLV is encountered by WriteHeader and decremented whenever the corresponding EndOfContents is encountered. The depth is zero-indexed, where zero represents the (virtual) top-level TLV.

func (*Encoder) StackIndex

func (e *Encoder) StackIndex(i int) Header

StackIndex returns information about the specified stack level. It must be a number between 0 and Encoder.StackDepth, inclusive.

The TLV header at level 0 represents the top level and is not written to the output. The top-level TLV header is a constructed, indefinite-length data value with tag 0.

func (*Encoder) WriteHeader

func (e *Encoder) WriteHeader(h Header) (io.WriteCloser, error)

WriteHeader writes the next TLV header to the output. At the end of constructed TLVs, a Header with TagEndOfContents must be written (for both definite and indefinite-length encodings). Encoder validates that the written sequence of headers and values is valid and will return an error if h cannot be written at the current place in the TLV structure.

If h indicates the use of the primitive encoding, WriteHeader returns an io.WriteCloser that can be used to write the contents of the value. It also implements io.ByteWriter. Before the next call to WriteHeader, the full value (as indicated by h.Length) must be written and io.Closer.Close must be called.

WriteHeader can be used in presence of transient errors. If the underlying writer returns an error during the write operation, WriteHeader will return that error (potentially wrapped). If the underlying writer maintains a consistent state after an error, you can retry the WriteHeader operation (using the same value for h) to resume the previous write operation.

type Header struct {
	Tag         asn1.Tag
	Constructed bool
	Length      int
}

Header represents a TLV header. The [Header.Length] may be LengthIndefinite if an indefinite-length encoding is used. It is invalid to use the indefinite-length encoding when [Header.Constructed] = false.

func (Header) String

func (h Header) String() string

String returns a string representation of h.

type Sequence

type Sequence struct {
	Tag asn1.Tag
	// contains filtered or unexported fields
}

Sequence can be used to build constructed TLVs for writing. Despite it name it can be used to build any constructed value, not just SEQUENCE values. The zero value is an empty constructed value. Note that Tag must be set before the value is written.

Example
var out bytes.Buffer
enc := NewEncoder(&out)
seq := Sequence{Tag: asn1.TagSequence}
// append a value to the sequence
seq.Append(func(enc *Encoder) error {
	val, err := enc.WriteHeader(Header{asn1.TagInteger, false, 1})
	if err != nil {
		return err
	}
	err = val.(io.ByteWriter).WriteByte(0x15)
	if err != nil {
		return err
	}
	return val.Close()
})
if err := seq.WriteTo(enc); err != nil {
	panic(err)
}
fmt.Printf("%# x\n", out.Bytes())
Output:

0x30 0x03 0x02 0x01 0x15

func (*Sequence) Append

func (s *Sequence) Append(val ...func(*Encoder) error)

Append adds the given values to the end of the sequence. A value is a function that encodes a single value into an Encoder. This function does not call any of the value functions.

func (*Sequence) WriteTo

func (s *Sequence) WriteTo(enc *Encoder) error

WriteTo encodes the values of s into enc. Writing is a three-step process:

  1. All values are encoded until they call Encoder.WriteHeader for the first time, at which point encoding pauses.
  2. The total length of all the values is calculated and an appropriate header for the sequence is written.
  3. The encoding of the individual values is resumed and each value is written to enc.

type SyntaxError

type SyntaxError struct {
	Err error // underlying error

	// ByteOffset is the location of the error. The location is usually the start of
	// the TLV header containing the error.
	ByteOffset int64

	// Header is the TLV header of the constructed TLV whose value contained the
	// malformed data.
	Header Header
	// contains filtered or unexported fields
}

SyntaxError represents an error in the TLV encoding. The error value contains the location of the error within the input as well as the Header of the surrounding data value.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

func (*SyntaxError) Unwrap

func (e *SyntaxError) Unwrap() error

Jump to

Keyboard shortcuts

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