summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-05-10 03:59:33 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-05-10 03:59:33 -0600
commita16049ef805c0c08b90885a5b7dfea7f74e51c5f (patch)
tree5b5dad8b0d44ccd3e7a43ac5bde557ae220a0f04
initial commit
-rw-r--r--cmd/btrfs-dbg/main.go105
-rw-r--r--cmd/btrfs-dbg/types.go80
-rw-r--r--go.mod5
-rw-r--r--go.sum11
-rw-r--r--pkg/binstruct/binstruct_test.go49
-rw-r--r--pkg/binstruct/l1.go163
-rw-r--r--pkg/binstruct/l2.go156
-rw-r--r--pkg/binstruct/l3.go61
8 files changed, 630 insertions, 0 deletions
diff --git a/cmd/btrfs-dbg/main.go b/cmd/btrfs-dbg/main.go
new file mode 100644
index 0000000..8758cd0
--- /dev/null
+++ b/cmd/btrfs-dbg/main.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "lukeshu.com/btrfs-tools/pkg/binstruct"
+)
+
+func main() {
+ if err := Main(os.Args[1]); err != nil {
+ fmt.Fprintf(os.Stderr, "%s: error: %v\n", os.Args[0], err)
+ os.Exit(1)
+ }
+}
+
+func Main(imgfilename string) (err error) {
+ maybeSetErr := func(_err error) {
+ if _err != nil && err == nil {
+ err = _err
+ }
+ }
+
+ fh, err := os.Open(imgfilename)
+ if err != nil {
+ return err
+ }
+ img := &Img{
+ File: fh,
+ }
+ defer func() {
+ maybeSetErr(img.Close())
+ }()
+
+ superblocks, err := img.Superblocks()
+ if err != nil {
+ return err
+ }
+ fmt.Printf("%#v\n", superblocks[0])
+
+ return nil
+}
+
+type Img struct {
+ *os.File
+}
+
+func (img *Img) Size() (int64, error) {
+ fi, err := img.Stat()
+ if err != nil {
+ return 0, err
+ }
+ return fi.Size(), nil
+}
+
+type Ref[T any] struct {
+ img *Img
+ addr int64
+ data T
+}
+
+func (r *Ref[T]) Read() error {
+ size, err := binstruct.Size(r.data)
+ if err != nil {
+ return err
+ }
+ buf := make([]byte, size)
+ if _, err := r.img.ReadAt(buf, r.addr); err != nil {
+ return err
+ }
+ return binstruct.Unmarshal(buf, &r.data)
+}
+
+func (img *Img) Superblocks() ([]Ref[Superblock], error) {
+ const superblockSize = 0x1000
+
+ var superblockAddrs = []int64{
+ 0x00_0001_0000, // 64KiB
+ 0x00_0400_0000, // 64MiB
+ 0x40_0000_0000, // 256GiB
+ }
+
+ sz, err := img.Size()
+ if err != nil {
+ return nil, err
+ }
+
+ var ret []Ref[Superblock]
+ for i, addr := range superblockAddrs {
+ if addr+superblockSize <= sz {
+ superblock := Ref[Superblock]{
+ img: img,
+ addr: addr,
+ }
+ if err := superblock.Read(); err != nil {
+ return nil, fmt.Errorf("superblock %d: %w", i, err)
+ }
+ ret = append(ret, superblock)
+ }
+ }
+ if len(ret) == 0 {
+ return nil, fmt.Errorf("no superblocks")
+ }
+ return ret, nil
+}
diff --git a/cmd/btrfs-dbg/types.go b/cmd/btrfs-dbg/types.go
new file mode 100644
index 0000000..33f78ba
--- /dev/null
+++ b/cmd/btrfs-dbg/types.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "lukeshu.com/btrfs-tools/pkg/binstruct"
+)
+
+type (
+ PhysicalAddr int64
+ LogicalAddr int64
+ UUID [16]byte
+)
+
+type Superblock struct {
+ Checksum [0x20]byte `bin:"off=0, siz=20, desc=Checksum of everything past this field (from 20 to 1000)"`
+ FSUUID UUID `bin:"off=20, siz=10, desc=FS UUID"`
+ Self PhysicalAddr `bin:"off=30, siz=8, desc=physical address of this block (different for mirrors)"`
+ Flags uint64 `bin:"off=38, siz=8, desc=flags"`
+ Magic [8]byte `bin:"off=40, siz=8, desc=magic ('_BHRfS_M')"`
+ Generation uint64 `bin:"off=48, siz=8, desc=generation"`
+
+ RootTree LogicalAddr `bin:"off=50, siz=8, desc=logical address of the root tree root"`
+ ChunkTree LogicalAddr `bin:"off=58, siz=8, desc=logical address of the chunk tree root"`
+ LogTree LogicalAddr `bin:"off=60, siz=8, desc=logical address of the log tree root"`
+
+ LogRootTransID uint64 `bin:"off=68, siz=8, desc=log_root_transid"`
+ TotalBytes uint64 `bin:"off=70, siz=8, desc=total_bytes"`
+ BytesUsed uint64 `bin:"off=78, siz=8, desc=bytes_used"`
+ RootDirObjectID uint64 `bin:"off=80, siz=8, desc=root_dir_objectid (usually 6)"`
+ NumDevices uint64 `bin:"off=88, siz=8, desc=num_devices"`
+
+ SectorSize uint32 `bin:"off=90, siz=4, desc=sectorsize"`
+ NodeSize uint32 `bin:"off=94, siz=4, desc=nodesize"`
+ LeafSize uint32 `bin:"off=98, siz=4, desc=leafsize"`
+ StripeSize uint32 `bin:"off=9c, siz=4, desc=stripesize"`
+ SysChunkArraySize uint32 `bin:"off=a0, siz=4, desc=sys_chunk_array_size"`
+
+ ChunkRootGeneration uint64 `bin:"off=a4, siz=8, desc=chunk_root_generation"`
+ CompatFlags uint64 `bin:"off=ac, siz=8, desc=compat_flags"`
+ CompatROFlags uint64 `bin:"off=b4, siz=8, desc=compat_ro_flags - only implementations that support the flags can write to the filesystem"`
+ IncompatFlags uint64 `bin:"off=bc, siz=8, desc=incompat_flags - only implementations that support the flags can use the filesystem"`
+ ChecksumType uint16 `bin:"off=c4, siz=2, desc=csum_type - Btrfs currently uses the CRC32c little-endian hash function with seed -1."`
+
+ RootLevel uint8 `bin:"off=c6, siz=1, desc=root_level"`
+ ChunkLevel uint8 `bin:"off=c7, siz=1, desc=chunk_root_level"`
+ LogLevel uint8 `bin:"off=c8, siz=1, desc=log_root_level"`
+
+ DevItem DevItem `bin:"off=c9, siz=62, desc=DEV_ITEM data for this device"`
+ Label [0x100]byte `bin:"off=12b, siz=100, desc=label (may not contain '/' or '\\')"`
+ CacheGeneration uint64 `bin:"off=22b, siz=8, desc=cache_generation"`
+ UUIDTreeGeneration uint64 `bin:"off=233, siz=8, desc=uuid_tree_generation"`
+ Reserved [0xf0]byte `bin:"off=23b, siz=f0, desc=reserved /* future expansion */"`
+ SysChunkArray [0x800]byte `bin:"off=32b, siz=800, desc=sys_chunk_array:(n bytes valid) Contains (KEY . CHUNK_ITEM) pairs for all SYSTEM chunks. This is needed to bootstrap the mapping from logical addresses to physical. "`
+ SuperRoots [0x2a0]byte `bin:"off=b2b, siz=2a0, desc=Contain super_roots (4 btrfs_root_backup)"`
+ Unused [0x235]byte `bin:"off=dcb, siz=235, desc=current unused"`
+
+ binstruct.End `bin:"off=1000"`
+}
+
+type DevItem struct {
+ DeviceID uint64 `bin:"off=0, siz=8, desc=device id"`
+
+ NumBytes uint64 `bin:"off=8, siz=8, desc=number of bytes"`
+ NumBytesUsed uint64 `bin:"off=10, siz=8, desc=number of bytes used"`
+
+ IOOptimalAlign uint32 `bin:"off=18, siz=4, desc=optimal I/O align"`
+ IOOptimalWidth uint32 `bin:"off=1c, siz=4, desc=optimal I/O width"`
+ IOMinSize uint32 `bin:"off=20, siz=4, desc=minimal I/O size (sector size)"`
+
+ Type uint64 `bin:"off=24, siz=8, desc=type"`
+ Generation uint64 `bin:"off=2c, siz=8, desc=generation"`
+ StartOffset uint64 `bin:"off=34, siz=8, desc=start offset"`
+ DevGroup uint32 `bin:"off=3c, siz=4, desc=dev group"`
+ SeekSpeed uint8 `bin:"off=40, siz=1, desc=seek speed"`
+ Bandwidth uint8 `bin:"off=41, siz=1, desc=bandwidth"`
+
+ DevUUID UUID `bin:"off=42, siz=10, desc=device UUID"`
+ FSUUID UUID `bin:"off=52, siz=10, desc=FS UUID"`
+
+ binstruct.End `bin:"off=62"`
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..be29d20
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module lukeshu.com/btrfs-tools
+
+go 1.18
+
+require github.com/stretchr/testify v1.7.1
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2dca7c9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,11 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/binstruct/binstruct_test.go b/pkg/binstruct/binstruct_test.go
new file mode 100644
index 0000000..33a2f0a
--- /dev/null
+++ b/pkg/binstruct/binstruct_test.go
@@ -0,0 +1,49 @@
+package binstruct_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "lukeshu.com/btrfs-tools/pkg/binstruct"
+)
+
+func TestSmoke(t *testing.T) {
+ type UUID [16]byte
+ type DevItem struct {
+ DeviceID uint64 `bin:"off=0, siz=8, desc=device id"`
+
+ NumBytes uint64 `bin:"off=8, siz=8, desc=number of bytes"`
+ NumBytesUsed uint64 `bin:"off=10, siz=8, desc=number of bytes used"`
+
+ IOOptimalAlign uint32 `bin:"off=18, siz=4, desc=optimal I/O align"`
+ IOOptimalWidth uint32 `bin:"off=1c, siz=4, desc=optimal I/O width"`
+ IOMinSize uint32 `bin:"off=20, siz=4, desc=minimal I/O size (sector size)"`
+
+ Type uint64 `bin:"off=24, siz=8, desc=type"`
+ Generation uint64 `bin:"off=2c, siz=8, desc=generation"`
+ StartOffset uint64 `bin:"off=34, siz=8, desc=start offset"`
+ DevGroup uint32 `bin:"off=3c, siz=4, desc=dev group"`
+ SeekSpeed uint8 `bin:"off=40, siz=1, desc=seek speed"`
+ Bandwidth uint8 `bin:"off=41, siz=1, desc=bandwidth"`
+
+ DevUUID UUID `bin:"off=42, siz=10, desc=device UUID"`
+ FSUUID UUID `bin:"off=52, siz=10, desc=FS UUID"`
+
+ binstruct.End `bin:"off=62"`
+ }
+ type TestType struct {
+ Magic [5]byte `bin:"off=0,siz=5"`
+ Dev DevItem `bin:"off=5,siz=62"`
+
+ binstruct.End `bin:"off=67"`
+ }
+
+ input := TestType{}
+ copy(input.Magic[:], "mAgIc")
+ input.Dev.DeviceID = 12
+
+ bs, err := binstruct.Marshal(input)
+ assert.NoError(t, err)
+ assert.True(t, len(bs) == 0x67, "len(bs)=0x%x", len(bs))
+}
diff --git a/pkg/binstruct/l1.go b/pkg/binstruct/l1.go
new file mode 100644
index 0000000..c535d2e
--- /dev/null
+++ b/pkg/binstruct/l1.go
@@ -0,0 +1,163 @@
+package binstruct
+
+import (
+ "encoding/binary"
+ "fmt"
+ "reflect"
+)
+
+type handler interface {
+ Unmarshal(dat []byte) interface{}
+ Marshal(val interface{}) []byte
+ Size() int64
+}
+
+type primitive struct {
+ unmarshal func(dat []byte) interface{}
+ marshal func(val interface{}) []byte
+ size int64
+}
+
+func (p primitive) Unmarshal(dat []byte) interface{} { return p.unmarshal(dat) }
+func (p primitive) Marshal(val interface{}) []byte { return p.marshal(val) }
+func (p primitive) Size() int64 { return p.size }
+
+var _ handler = primitive{}
+
+func genHandler(typ reflect.Type) (handler, error) {
+ switch typ.Kind() {
+ case reflect.Invalid: // invalid
+ return nil, fmt.Errorf("unsupported kind: %s: %v", typ.Kind(), typ)
+ case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: // I don't wanna
+ return nil, fmt.Errorf("unsupported kind: %s: %v", typ.Kind(), typ)
+ case reflect.Int, reflect.Uint, reflect.Uintptr: // platform specific
+ return nil, fmt.Errorf("unsupported kind: %s: %v", typ.Kind(), typ)
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer: // runtime
+ return nil, fmt.Errorf("unsupported kind: %s: %v", typ.Kind(), typ)
+ case reflect.Map, reflect.Slice, reflect.String: // dynamic size
+ return nil, fmt.Errorf("unsupported kind: %s: %v", typ.Kind(), typ)
+
+ // uint ////////////////////////////////////////////////////////////////
+ case reflect.Uint8:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return dat[0] },
+ marshal: func(val interface{}) []byte { return []byte{val.(uint8)} },
+ size: 1,
+ }, nil
+ case reflect.Uint16:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return binary.LittleEndian.Uint16(dat) },
+ marshal: func(val interface{}) []byte {
+ var buf [2]byte
+ binary.LittleEndian.PutUint16(buf[:], val.(uint16))
+ return buf[:]
+ },
+ size: 2,
+ }, nil
+ case reflect.Uint32:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return binary.LittleEndian.Uint32(dat) },
+ marshal: func(val interface{}) []byte {
+ var buf [4]byte
+ binary.LittleEndian.PutUint32(buf[:], val.(uint32))
+ return buf[:]
+ },
+ size: 4,
+ }, nil
+ case reflect.Uint64:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return binary.LittleEndian.Uint64(dat) },
+ marshal: func(val interface{}) []byte {
+ var buf [8]byte
+ binary.LittleEndian.PutUint64(buf[:], val.(uint64))
+ return buf[:]
+ },
+ size: 8,
+ }, nil
+
+ // int /////////////////////////////////////////////////////////////////
+ case reflect.Int8:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return int8(dat[0]) },
+ marshal: func(val interface{}) []byte { return []byte{uint8(val.(int8))} },
+ size: 1,
+ }, nil
+ case reflect.Int16:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return int16(binary.LittleEndian.Uint16(dat)) },
+ marshal: func(val interface{}) []byte {
+ var buf [2]byte
+ binary.LittleEndian.PutUint16(buf[:], uint16(val.(int16)))
+ return buf[:]
+ },
+ size: 2,
+ }, nil
+ case reflect.Int32:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return int32(binary.LittleEndian.Uint32(dat)) },
+ marshal: func(val interface{}) []byte {
+ var buf [4]byte
+ binary.LittleEndian.PutUint32(buf[:], uint32(val.(int32)))
+ return buf[:]
+ },
+ size: 4,
+ }, nil
+ case reflect.Int64:
+ return primitive{
+ unmarshal: func(dat []byte) interface{} { return int64(binary.LittleEndian.Uint64(dat)) },
+ marshal: func(val interface{}) []byte {
+ var buf [8]byte
+ binary.LittleEndian.PutUint64(buf[:], uint64(val.(int64)))
+ return buf[:]
+ },
+ size: 8,
+ }, nil
+
+ // composite ///////////////////////////////////////////////////////////
+
+ case reflect.Ptr:
+ inner, err := getHandler(typ.Elem())
+ if err != nil {
+ return nil, fmt.Errorf("%v: %w", typ, err)
+ }
+ return primitive{
+ unmarshal: func(dat []byte) interface{} {
+ return reflect.ValueOf(inner.Unmarshal(dat)).Addr().Interface()
+ },
+ marshal: func(val interface{}) []byte {
+ return inner.Marshal(reflect.ValueOf(val).Elem().Interface())
+ },
+ size: inner.Size(),
+ }, nil
+ case reflect.Array:
+ inner, err := getHandler(typ.Elem())
+ if err != nil {
+ return nil, fmt.Errorf("%v: %w", typ, err)
+ }
+ return primitive{
+ unmarshal: func(dat []byte) interface{} {
+ val := reflect.Zero(typ)
+ for i := 0; i < typ.Len(); i++ {
+ fmt.Printf("%v[%d]: %v\n", typ, i, val.Index(i))
+ val.Index(i).Set(reflect.ValueOf(inner.Unmarshal(dat[i*int(inner.Size()):])))
+ }
+ return val.Interface()
+ },
+ marshal: func(val interface{}) []byte {
+ _val := reflect.ValueOf(val)
+ var ret []byte
+ for i := 0; i < typ.Len(); i++ {
+ ret = append(ret, inner.Marshal(_val.Index(i).Interface())...)
+ }
+ return ret
+ },
+ size: inner.Size() * int64(typ.Len()),
+ }, nil
+ case reflect.Struct:
+ return genStructHandler(typ)
+
+ // end /////////////////////////////////////////////////////////////////
+ default:
+ panic(fmt.Errorf("unknown kind: %v: %v", typ.Kind(), typ))
+ }
+}
diff --git a/pkg/binstruct/l2.go b/pkg/binstruct/l2.go
new file mode 100644
index 0000000..005d686
--- /dev/null
+++ b/pkg/binstruct/l2.go
@@ -0,0 +1,156 @@
+package binstruct
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+type End struct{}
+
+type structHandler struct {
+ typ reflect.Type
+ fields []structField
+ size int64
+}
+
+type structField struct {
+ tag
+ handler
+ name string
+}
+
+func (sh structHandler) Unmarshal(dat []byte) interface{} {
+ val := reflect.Zero(sh.typ)
+ for i, field := range sh.fields {
+ if field.skip {
+ continue
+ }
+ val.Field(i).Set(reflect.ValueOf(field.Unmarshal(dat[field.off:])))
+ }
+ return val.Interface()
+}
+func (sh structHandler) Marshal(_val interface{}) []byte {
+ val := reflect.ValueOf(_val)
+ ret := make([]byte, 0, sh.size)
+ for i, field := range sh.fields {
+ if field.skip {
+ continue
+ }
+ if int64(len(ret)) != field.off {
+ panic(fmt.Errorf("field %d %q: len(ret)=0x%x but field.off=0x%x", i, field.name, len(ret), field.off))
+ }
+ ret = append(ret, field.Marshal(val.Field(i).Interface())...)
+ }
+ return ret
+}
+func (sh structHandler) Size() int64 {
+ return sh.size
+}
+
+var _ handler = structHandler{}
+
+func genStructHandler(structInfo reflect.Type) (handler, error) {
+ ret := structHandler{
+ typ: structInfo,
+ }
+
+ var curOffset, endOffset int64
+ for i := 0; i < structInfo.NumField(); i++ {
+ var fieldInfo reflect.StructField = structInfo.Field(i)
+
+ fieldTag, err := parseStructTag(fieldInfo.Tag.Get("bin"))
+ if err != nil {
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ if fieldTag.skip {
+ ret.fields = append(ret.fields, structField{
+ tag: fieldTag,
+ name: fieldInfo.Name,
+ })
+ continue
+ }
+
+ if fieldTag.off != curOffset {
+ err := fmt.Errorf("tag says off=0x%x but curOffset=0x%x", fieldTag.off, curOffset)
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ if fieldInfo.Type == reflect.TypeOf(End{}) {
+ endOffset = curOffset
+ }
+
+ fieldHandler, err := getHandler(fieldInfo.Type)
+ if err != nil {
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+
+ if fieldTag.siz != fieldHandler.Size() {
+ err := fmt.Errorf("tag says siz=0x%x but handler.Size()=0x%x", fieldTag.siz, fieldHandler.Size())
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ curOffset += fieldTag.siz
+
+ ret.fields = append(ret.fields, structField{
+ tag: fieldTag,
+ handler: fieldHandler,
+ name: fieldInfo.Name,
+ })
+ }
+ ret.size = curOffset
+
+ if ret.size != endOffset {
+ return nil, fmt.Errorf("%v: .size=%v but endOffset=%v",
+ structInfo, ret.size, endOffset)
+ }
+
+ return ret, nil
+}
+
+type tag struct {
+ skip bool
+
+ off int64
+ siz int64
+ desc string
+}
+
+func parseStructTag(str string) (tag, error) {
+ var ret tag
+ for _, part := range strings.Split(str, ",") {
+ part = strings.TrimSpace(part)
+ if part == "" {
+ continue
+ }
+ if part == "-" {
+ return tag{skip: true}, nil
+ }
+ keyval := strings.SplitN(part, "=", 2)
+ if len(keyval) != 2 {
+ return tag{}, fmt.Errorf("option is not a key=value pair: %q", part)
+ }
+ key := keyval[0]
+ val := keyval[1]
+ switch key {
+ case "off":
+ vint, err := strconv.ParseInt(val, 16, 64)
+ if err != nil {
+ return tag{}, err
+ }
+ ret.off = vint
+ case "siz":
+ vint, err := strconv.ParseInt(val, 16, 64)
+ if err != nil {
+ return tag{}, err
+ }
+ ret.siz = vint
+ case "desc":
+ ret.desc = val
+ }
+ }
+ return ret, nil
+}
diff --git a/pkg/binstruct/l3.go b/pkg/binstruct/l3.go
new file mode 100644
index 0000000..f9fb8b1
--- /dev/null
+++ b/pkg/binstruct/l3.go
@@ -0,0 +1,61 @@
+package binstruct
+
+import (
+ "fmt"
+ "reflect"
+)
+
+var handlerCache = make(map[reflect.Type]handler)
+
+func getHandler(typ reflect.Type) (handler, error) {
+ h, ok := handlerCache[typ]
+ if ok {
+ return h, nil
+ }
+
+ h, err := genHandler(typ)
+ if err != nil {
+ return nil, err
+ }
+ handlerCache[typ] = h
+ return h, nil
+}
+
+func Unmarshal(dat []byte, dst interface{}) error {
+ _dst := reflect.ValueOf(dst)
+ if _dst.Kind() != reflect.Ptr {
+ return fmt.Errorf("not a pointer: %v", _dst.Type())
+ }
+ handler, err := getHandler(_dst.Type().Elem())
+ if err != nil {
+ return err
+ }
+ if int64(len(dat)) < handler.Size() {
+ return fmt.Errorf("need at least %d bytes of data, only have %d",
+ handler.Size(), len(dat))
+ }
+ val := handler.Unmarshal(dat[:handler.Size()])
+ _dst.Elem().Set(reflect.ValueOf(val))
+ return nil
+}
+
+func Marshal(val interface{}) ([]byte, error) {
+ handler, err := getHandler(reflect.TypeOf(val))
+ if err != nil {
+ return nil, err
+ }
+ bs := handler.Marshal(val)
+ if int64(len(bs)) != handler.Size() {
+ return nil, fmt.Errorf("got %d bytes but expected %d bytes",
+ len(bs), handler.Size())
+ }
+ return bs, nil
+}
+
+func Size(val interface{}) (int64, error) {
+ handler, err := getHandler(reflect.TypeOf(val))
+ if err != nil {
+ return 0, err
+ }
+ return handler.Size(), nil
+}