Improve HomeKit TLV format parser

This commit is contained in:
Alex X
2025-09-19 15:29:24 +03:00
parent 40269328fb
commit 3b976c6812
2 changed files with 133 additions and 45 deletions

View File

@@ -46,6 +46,8 @@ func Marshal(v any) ([]byte, error) {
} }
switch kind { switch kind {
case reflect.Slice:
return appendSlice(nil, value)
case reflect.Struct: case reflect.Struct:
return appendStruct(nil, value) return appendStruct(nil, value)
} }
@@ -53,6 +55,23 @@ func Marshal(v any) ([]byte, error) {
return nil, errors.New("tlv8: not implemented: " + kind.String()) return nil, errors.New("tlv8: not implemented: " + kind.String())
} }
// separator the most confusing meaning in the documentation.
// It can have a value of 0x00 or 0xFF or even 0x05.
const separator = 0xFF
func appendSlice(b []byte, value reflect.Value) ([]byte, error) {
for i := 0; i < value.Len(); i++ {
if i > 0 {
b = append(b, separator, 0)
}
var err error
if b, err = appendStruct(b, value.Index(i)); err != nil {
return nil, err
}
}
return b, nil
}
func appendStruct(b []byte, value reflect.Value) ([]byte, error) { func appendStruct(b []byte, value reflect.Value) ([]byte, error) {
valueType := value.Type() valueType := value.Type()
@@ -121,7 +140,7 @@ func appendValue(b []byte, tag byte, value reflect.Value) ([]byte, error) {
case reflect.Slice: case reflect.Slice:
for i := 0; i < value.Len(); i++ { for i := 0; i < value.Len(); i++ {
if i > 0 { if i > 0 {
b = append(b, 0, 0) b = append(b, separator, 0)
} }
if b, err = appendValue(b, tag, value.Index(i)); err != nil { if b, err = appendValue(b, tag, value.Index(i)); err != nil {
return nil, err return nil, err
@@ -179,64 +198,86 @@ func Unmarshal(data []byte, v any) error {
kind = value.Kind() kind = value.Kind()
} }
if kind != reflect.Struct { switch kind {
return errors.New("tlv8: not implemented: " + kind.String()) case reflect.Slice:
return unmarshalSlice(data, value)
case reflect.Struct:
return unmarshalStruct(data, value)
} }
return unmarshalStruct(data, value) return errors.New("tlv8: not implemented: " + kind.String())
} }
func unmarshalStruct(b []byte, value reflect.Value) error { // unmarshalTLV can return two types of errors:
var waitSlice bool // - critical and then the value of []byte will be nil
// - not critical and then []byte will contain the value
func unmarshalTLV(b []byte, value reflect.Value) ([]byte, error) {
if len(b) < 2 {
return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
}
for len(b) >= 2 { t := b[0]
t := b[0] l := int(b[1])
l := int(b[1])
// array item divider // array item divider (t == 0x00 || t == 0xFF)
if t == 0 && l == 0 { if l == 0 {
b = b[2:] return b[2:], errors.New("tlv8: zero item")
waitSlice = true }
continue
var v []byte
for {
if len(b) < 2+l {
return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
} }
var v []byte v = append(v, b[2:2+l]...)
b = b[2+l:]
for { // if size == 255 and same tag - continue read big payload
if len(b) < 2+l { if l < 255 || len(b) < 2 || b[0] != t {
return errors.New("tlv8: wrong size: " + value.Type().Name()) break
}
l = int(b[1])
}
tag := strconv.Itoa(int(t))
valueField, ok := getStructField(value, tag)
if !ok {
return b, fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
}
if err := unmarshalValue(v, valueField); err != nil {
return nil, err
}
return b, nil
}
func unmarshalSlice(b []byte, value reflect.Value) error {
valueIndex := value.Index(growSlice(value))
for len(b) > 0 {
var err error
if b, err = unmarshalTLV(b, valueIndex); err != nil {
if b != nil {
valueIndex = value.Index(growSlice(value))
continue
} }
v = append(v, b[2:2+l]...)
b = b[2+l:]
// if size == 255 and same tag - continue read big payload
if l < 255 || len(b) < 2 || b[0] != t {
break
}
l = int(b[1])
}
tag := strconv.Itoa(int(t))
valueField, ok := getStructField(value, tag)
if !ok {
return fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
}
if waitSlice {
if valueField.Kind() != reflect.Slice {
return fmt.Errorf("tlv8: should be slice T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
}
waitSlice = false
}
if err := unmarshalValue(v, valueField); err != nil {
return err return err
} }
} }
return nil
}
func unmarshalStruct(b []byte, value reflect.Value) error {
for len(b) > 0 {
var err error
if b, err = unmarshalTLV(b, value); b == nil && err != nil {
return err
}
}
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package tlv8
import ( import (
"encoding/hex" "encoding/hex"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -107,3 +108,49 @@ func TestInterface(t *testing.T) {
require.Equal(t, src, dst) require.Equal(t, src, dst)
} }
func TestSlice1(t *testing.T) {
var v struct {
VideoAttrs []struct {
Width uint16 `tlv8:"1"`
Height uint16 `tlv8:"2"`
Framerate uint8 `tlv8:"3"`
} `tlv8:"3"`
}
s := `030b010280070202380403011e ff00 030b010200050202d00203011e`
b1, err := hex.DecodeString(strings.ReplaceAll(s, " ", ""))
require.NoError(t, err)
err = Unmarshal(b1, &v)
require.NoError(t, err)
require.Len(t, v.VideoAttrs, 2)
b2, err := Marshal(v)
require.NoError(t, err)
require.Equal(t, b1, b2)
}
func TestSlice2(t *testing.T) {
var v []struct {
Width uint16 `tlv8:"1"`
Height uint16 `tlv8:"2"`
Framerate uint8 `tlv8:"3"`
}
s := `010280070202380403011e ff00 010200050202d00203011e`
b1, err := hex.DecodeString(strings.ReplaceAll(s, " ", ""))
require.NoError(t, err)
err = Unmarshal(b1, &v)
require.NoError(t, err)
require.Len(t, v, 2)
b2, err := Marshal(v)
require.NoError(t, err)
require.Equal(t, b1, b2)
}