Skip to content

Commit d04fc8e

Browse files
committed
rework: reflection-based Marshal/Unmarshal API
Replace the Parse/Builder API with a struct tag-based encoding/decoding API similar to encoding/json. This is a breaking change from v0.1.x. - Add Marshal/Unmarshal with struct tag support (hex 0x and decimal formats) - Add Encoder/Decoder types for streaming usage - Add Marshaler/Unmarshaler interfaces for custom types - Add Options with DisallowUnknownFields - Remove all external dependencies - Replace Travis CI with GitHub Actions - Add Nix flake with devshell and treefmt (gofumpt + golangci-lint) - Add example tests for all public functions - Update README, LICENSE, go.mod
1 parent 4ccd4ca commit d04fc8e

22 files changed

Lines changed: 4587 additions & 1962 deletions

.envrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then
2+
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM="
3+
fi
4+
5+
use flake

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
go-version: ['1.18', '1.26']
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-go@v5
20+
with:
21+
go-version: ${{ matrix.go-version }}
22+
23+
- name: Test
24+
run: go test -race -coverprofile=coverage.txt ./...
25+
26+
- name: Upload coverage
27+
if: matrix.go-version == '1.26'
28+
uses: codecov/codecov-action@v4
29+
with:
30+
files: coverage.txt

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
# IDE
18+
.idea/
19+
20+
# direnv
21+
.direnv/

.travis.yml

Lines changed: 0 additions & 14 deletions
This file was deleted.

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2021 skythen
3+
Copyright (c) 2021-2026 skythen
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

README.md

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,132 @@
11
# BER-TLV
22

3-
[![Build Status](https://travis-ci.org/skythen/bertlv.svg?branch=master)](https://travis-ci.org/skythen/bertlv)
4-
[![Coverage Status](https://coveralls.io/repos/github/skythen/bertlv/badge.svg?branch=master)](https://coveralls.io/github/skythen/bertlv?branch=master)
5-
[![GoDoc](https://godoc.org/github.com/skythen/bertlv?status.svg)](http://godoc.org/github.com/skythen/bertlv)
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/skythen/bertlv.svg)](https://pkg.go.dev/github.com/skythen/bertlv)
64
[![Go Report Card](https://goreportcard.com/badge/github.com/skythen/bertlv)](https://goreportcard.com/report/github.com/skythen/bertlv)
75

8-
Package bertlv implements parsing and building of BER-TLV structures.
9-
10-
Please note that this is not a complete implementation of the X.690 standard as it is agnostic about classes (Universal, Application, Context-specific, Private) and therefore does not check for correct encoding of tags/values.
6+
Package bertlv implements encoding and decoding of BER-TLV structures using struct tags, similar to `encoding/json`.
117

128
`go get github.com/skythen/bertlv`
139

14-
## Parse
10+
## Struct Tags
11+
12+
Use the `bertlv` struct tag to map struct fields to BER-TLV tags.
13+
14+
### Hex Format
15+
16+
Use the `0x` prefix to specify tags as hex bytes. Class and construction are derived from the encoded tag bytes:
17+
18+
```go
19+
type FCI struct {
20+
AID []byte `bertlv:"0x4F"`
21+
Label []byte `bertlv:"0x50"`
22+
Track2 []byte `bertlv:"0x57"`
23+
ExpDate []byte `bertlv:"0x5F24"` // multi-byte tags
24+
Records []Record `bertlv:"0x70"` // constructed tags decode recursively
25+
}
26+
```
27+
28+
### Decimal Format
29+
30+
Specify tag number and class explicitly:
31+
32+
`"<number>,<class>[,options...]"`
33+
34+
- **number**: Decimal tag number (0-16383)
35+
- **class**: One of `universal`, `application`, `context`, `private`
36+
37+
```go
38+
type Data struct {
39+
Field1 []byte `bertlv:"1,universal"`
40+
Field2 []byte `bertlv:"2,application"`
41+
Field3 []byte `bertlv:"3,context"`
42+
}
43+
```
44+
45+
### Options
46+
47+
- `omitempty` - skip field during encoding if empty/zero
48+
- `required` - return error during decoding if tag is not present
49+
50+
```go
51+
type Data struct {
52+
Required []byte `bertlv:"0x4F,required"`
53+
Optional []byte `bertlv:"0x50,omitempty"`
54+
Both []byte `bertlv:"1,universal,required,omitempty"`
55+
}
56+
```
57+
58+
## Unmarshal
1559

16-
You can parse BER-TLV encoded data from bytes:
60+
Decode BER-TLV data into a struct:
1761

1862
```go
19-
b := []byte{0x71, 0x10, 0xB0, 0x0E, 0x0F, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0E, 0x05, 0x05, 0x04, 0x03, 0x02, 0x01}
20-
bertlvs, err := Parse(b)
63+
var result struct {
64+
AID []byte `bertlv:"0x4F"`
65+
Label []byte `bertlv:"0x50"`
66+
}
67+
68+
err := bertlv.Unmarshal(data, &result)
2169
```
22-
### Constructed objects
23-
You can check if a BerTLV is constructed and get first or all children or filter child objects by tag:
70+
71+
### Options
72+
2473
```go
25-
if bertlvs[0].Tag.IsConstructed() {
26-
child := bertlvs[0].FirstChild(NewOneByteTag(0x0F))
74+
opts := bertlv.Options{
75+
DisallowUnknownFields: true,
2776
}
77+
78+
err := bertlv.UnmarshalWithOptions(data, &result, opts)
2879
```
2980

30-
## Create
31-
You can create single BER-TLVs with NewBerTLV:
81+
### Custom Unmarshaler
82+
83+
Implement the `Unmarshaler` interface for custom decoding:
84+
3285
```go
33-
val := []byte{0xB0, 0x0E, 0x0F, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0E, 0x05, 0x05, 0x04, 0x03, 0x02, 0x01}
34-
bertlv, err := NewBerTLV(NewOneByteTag(0x71), val)
86+
type MyType struct {
87+
Value string
88+
}
89+
90+
func (m *MyType) UnmarshalBERTLV(value []byte) error {
91+
m.Value = string(value)
92+
return nil
93+
}
3594
```
3695

37-
If you want to create complex constructed objects you use the Builder:
96+
## Marshal
97+
98+
Encode a struct into BER-TLV format:
99+
38100
```go
39-
builder := Builder{}
40-
nestedBuilder := Builder{}
41-
anotherNestedBuilder := Builder{}
101+
data := struct {
102+
AID []byte `bertlv:"0x4F"`
103+
Label []byte `bertlv:"0x50"`
104+
}{
105+
AID: []byte{0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10},
106+
Label: "GoCard",
107+
}
42108

43-
berTlvs, err := builder.AddBytes(NewOneByteTag(0x71), // first level, constructed object
44-
nestedBuilder.AddBytes(NewOneByteTag(0xB0), // second level, constructed object
45-
anotherNestedBuilder.
46-
AddBytes(NewOneByteTag(0x0F), []byte{0x01, 0x02, 0x03, 0x04, 0x05}). // third level primitive object
47-
AddBytes(NewOneByteTag(0x0E), []byte{0x05, 0x04, 0x03, 0x02, 0x01}). // third level primitive object
48-
Bytes()).
49-
Bytes()).
50-
BuildBerTLVs()
109+
encoded, err := bertlv.Marshal(data)
51110
```
52111

53-
You can also use
54-
- builder.AddEmpty() to add objects without value
55-
- builder.AddByte() to add objects with a value that consists of a single byte
56-
- builder.AddRaw() to add raw bytes
112+
### Custom Marshaler
113+
114+
Implement the `Marshaler` interface for custom encoding:
115+
116+
```go
117+
func (m MyType) MarshalBERTLV() ([]byte, error) {
118+
return []byte(m.Value), nil
119+
}
120+
```
121+
122+
## Supported Types
123+
124+
The following Go types can be used as struct fields:
125+
126+
- `[]byte` - raw TLV value
127+
- `string` - UTF-8 encoded value
128+
- `int`, `int8`, `int16`, `int32`, `int64` - big-endian signed integers
129+
- `uint`, `uint8`, `uint16`, `uint32`, `uint64` - big-endian unsigned integers
130+
- `bool` - single byte (0x00 = false, non-zero = true)
131+
- `struct` - nested constructed TLV
132+
- `[]T` (slice of structs) - repeated TLV elements

0 commit comments

Comments
 (0)