Skip to content

Commit

Permalink
fix(nodes): prevent nil pointers in node parsing (#23)
Browse files Browse the repository at this point in the history
* fix(nodes): prevent nil pointers in node parsing

* chore(docs): improve go doc

* chore(lint): add godot to linters
  • Loading branch information
pauloavelar committed Jan 7, 2023
1 parent fd30700 commit 3a2f172
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 78 deletions.
7 changes: 6 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ linters:
- goconst # Enforces constants are created for repeated strings
- gocritic # Offers an opinionated set of best practices
- gocyclo # Analyzes code complexity (cyclomatic)
- gosimple # Offers code simplification suggestions
- godot # Enforces comments always end with a period
- gofmt # Enforces source code is properly formatted
- gomnd # Forbids magic numbers from being used without declaration
- gosec # Inspects source code for security problems
- gosimple # Offers code simplification suggestions
- govet # Reports suspicious construct (e.g. Printf with bad parameter count)
- ineffassign # Detects unused existing variable assignments
- lll # Limits maximum line lengths
Expand Down Expand Up @@ -72,6 +73,10 @@ linters-settings:
- whyNoLint
gocyclo:
min-complexity: 8
godot:
exclude:
- "^(FIXME|TODO) " # technical comments not meant for go doc
- "\\*$" # multiline comments with asterisk indentation
gofmt:
simplify: true
govet:
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ nodes** found in the payload.

> ⚠️  The decoder works in an all or none strategy when dealing with multiple messages.
### Manually-created nodes use the default decoder configuration

When a `tlv.Node` is created by declaring the struct, all methods that require context, such as `GetNodes`
or `GetUint8` (or any other integer parser), will use the **standard decoder** definitions. See above for
more details on the decoder. To create a node with custom decoder configuration, first create a decoder
and call the `NewNode` method on it.


```go
var node tlv.Node

node = tlv.Node{Tag: Tag(0x1234), Value: []byte{1}}
node.GetNodes() // uses the standard decoder configuration

customDecoder := tlv.MustCreateDecoder(1, 1, binary.LittleEndian)
node = customDecoder.NewNode(Tag(0x1234), []byte{1})
node.GetNodes() // uses the customDecoder configuration
```

## Caveats

### No bit parity or checksum
Expand All @@ -145,6 +164,11 @@ means none of it can be trusted.
## Changelog

* **`v1.1.0`** (2023-06-01)
* [#23](https://github.com/pauloavelar/go-tlv/pull/23): nil pointer errors on manually-created nodes
* fix panics when calling value getters on a node without a decoder reference
* provide functions to create a Node with the proper configuration (standard or custom)

* **`v1.0.0`** (2022-07-01)
* **Breaking** change: parser has been renamed to decoder
* [#10](https://github.com/pauloavelar/go-tlv/issues/10): add support to custom tag and length sizes
Expand Down
88 changes: 53 additions & 35 deletions tlv/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@ import (
"github.com/pauloavelar/go-tlv/tlv/internal/utils"
)

// Decoder is a TLV decoder with custom configuration.
// Decoder is a configurable TLV decoder instance.
type Decoder interface {
// DecodeReader decodes the whole reader to a list of TLV nodes
// DecodeReader decodes the entire reader data to a list of TLV [Nodes].
DecodeReader(reader io.Reader) (Nodes, error)
// DecodeBytes decodes a byte array to a list of TLV nodes
// DecodeBytes decodes a byte array to a list of TLV [Nodes].
DecodeBytes(data []byte) (Nodes, error)
// DecodeSingle decodes a byte array to a single TLV node
// DecodeSingle decodes a byte array to a single TLV [Node].
DecodeSingle(data []byte) (res Node, read uint64, err error)
// NewNode creates a new node using the decoder configuration.
NewNode(tag Tag, value []byte) Node
// GetByteOrder returns the decoder endianness configuration.
GetByteOrder() binary.ByteOrder
}

type decoder struct {
tagSize uint8
lengthSize uint8
minNodeSize uint8
binaryParser binary.ByteOrder
tagSize uint8
lengthSize uint8
minNodeSize uint8
byteOrder binary.ByteOrder
}

const (
Expand All @@ -32,7 +36,7 @@ const (
maxLenSize = 8 // 2^8 = 256
)

// MustCreateDecoder creates a decoder using custom configuration or panics in case of any errors.
// MustCreateDecoder creates a [Decoder] using custom configuration or panics in case of any errors.
func MustCreateDecoder(tagSize, lengthSize uint8, byteOrder binary.ByteOrder) Decoder {
res, err := CreateDecoder(tagSize, lengthSize, byteOrder)
if err != nil {
Expand All @@ -42,8 +46,8 @@ func MustCreateDecoder(tagSize, lengthSize uint8, byteOrder binary.ByteOrder) De
return res
}

// CreateDecoder creates a decoder using custom configuration.
// Hint: `tagSize` and `lengthSize` must be numbers between 1 and 8.
// CreateDecoder creates a [Decoder] using custom configuration.
// Hint: tagSize and lengthSize must be numbers between 1 and 8.
func CreateDecoder(tagSize, lengthSize uint8, byteOrder binary.ByteOrder) (Decoder, error) {
if tagSize < minTagSize || tagSize > maxTagSize {
return nil, errors.NewInvalidSizeError("tag", tagSize, minTagSize, maxTagSize)
Expand All @@ -54,29 +58,29 @@ func CreateDecoder(tagSize, lengthSize uint8, byteOrder binary.ByteOrder) (Decod
}

res := &decoder{
tagSize: tagSize,
lengthSize: lengthSize,
minNodeSize: tagSize + lengthSize,
binaryParser: byteOrder,
tagSize: tagSize,
lengthSize: lengthSize,
minNodeSize: tagSize + lengthSize,
byteOrder: byteOrder,
}

return res, nil
}

// DecodeReader decodes the full contents of a Reader as TLV nodes.
// DecodeReader decodes the full contents of a [io.Reader] as TLV [Nodes].
// Note: the current implementation loads the entire Reader data into memory.
func (p *decoder) DecodeReader(reader io.Reader) (Nodes, error) {
func (d *decoder) DecodeReader(reader io.Reader) (Nodes, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, err
}

return p.DecodeBytes(data)
return d.DecodeBytes(data)
}

// DecodeBytes decodes a byte array as TLV nodes.
func (p *decoder) DecodeBytes(data []byte) (Nodes, error) {
node, read, err := p.DecodeSingle(data)
// DecodeBytes decodes a byte array as TLV [Nodes].
func (d *decoder) DecodeBytes(data []byte) (Nodes, error) {
node, read, err := d.DecodeSingle(data)
if err != nil {
return nil, err
}
Expand All @@ -85,36 +89,50 @@ func (p *decoder) DecodeBytes(data []byte) (Nodes, error) {
return Nodes{node}, nil
}

next, err := p.DecodeBytes(data[read:])
next, err := d.DecodeBytes(data[read:])
if err != nil {
return nil, err
}

return append(Nodes{node}, next...), nil
}

// DecodeSingle decodes a byte array as a single TLV node.
func (p *decoder) DecodeSingle(data []byte) (res Node, read uint64, err error) {
if len(data) < int(p.minNodeSize) {
// DecodeSingle decodes a byte array as a single TLV [Node].
func (d *decoder) DecodeSingle(data []byte) (res Node, read uint64, err error) {
if len(data) < int(d.minNodeSize) {
return res, 0, errors.NewMessageTooShortError(data)
}

tag := utils.GetPaddedUint64(p.binaryParser, data[:p.tagSize])
length := utils.GetPaddedUint64(p.binaryParser, data[p.tagSize:p.minNodeSize])
messageLength := uint64(p.minNodeSize) + length
tag := utils.GetPaddedUint64(d.byteOrder, data[:d.tagSize])
length := utils.GetPaddedUint64(d.byteOrder, data[d.tagSize:d.minNodeSize])
messageLength := uint64(d.minNodeSize) + length

if len(data) < int(messageLength) {
return res, 0, errors.NewLengthMismatchError(length, data, p.minNodeSize)
return res, 0, errors.NewLengthMismatchError(length, data, d.minNodeSize)
}

node := Node{
Tag: Tag(tag),
Length: Length(length),
Value: data[p.minNodeSize:messageLength],
Raw: data[:messageLength],
decoder: p,
binParser: p.binaryParser,
Tag: Tag(tag),
Length: Length(length),
Value: data[d.minNodeSize:messageLength],
Raw: data[:messageLength],
decoder: d,
}

return node, messageLength, nil
}

// NewNode creates a new [Node] using the [Decoder] configuration.
func (d *decoder) NewNode(tag Tag, value []byte) Node {
return Node{
Tag: tag,
Length: Length(len(value)),
Value: value,
decoder: d,
}
}

// GetByteOrder returns the [Decoder] endianness configuration.
func (d *decoder) GetByteOrder() binary.ByteOrder {
return d.byteOrder
}
20 changes: 10 additions & 10 deletions tlv/doc.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
/*
Package tlv holds all logic to decode TLV messages into `Nodes`.
Package tlv holds all logic to decode TLV messages into [Nodes].
There are two ways to use this package: with the standard decoder or creating
a custom Decoder with custom tag/length sizes and byte order. The standard
decoder uses 2-byte tags, 2-byte lengths and big endian byte order.
a custom [Decoder] with custom tag/length sizes and byte order. The standard
decoder uses 2-byte tags, 2-byte lengths and big endian [binary.ByteOrder].
Nodes are a representation of a collection of decoded TLV messages.
Specific messages can be filtered by Tag and indexes can be accessed
[Nodes] are a representation of a collection of decoded TLV messages.
Specific messages can be filtered by [Tag] and indexes can be accessed
directly in an array-like syntax:
firstNode := nodes[0]
Node is a representation of a single TLV message, comprised of a Tag,
a Length and a Value. The struct has many helper methods to parse the
[Node] is a representation of a single TLV message, comprised of a [Tag],
a [Length] and a Value. The struct has many helper methods to parse the
value as different types (e.g. integers, strings, dates and booleans),
as well as nested TLV Nodes.
as well as nested TLV [Nodes].
node.GetNodes()
node.GetPaddedUint8()
Note: Nodes decoded with a custom configuration retain the configuration
when parsing their values as nodes, so messages always have consistent
Note: [Nodes] decoded with a custom configuration retain the configuration
when parsing their values as other nodes, so messages always have consistent
tag/length sizes and byte order.
*/
package tlv
Loading

0 comments on commit 3a2f172

Please sign in to comment.