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 {
case reflect.Slice:
return appendSlice(nil, value)
case reflect.Struct:
return appendStruct(nil, value)
}
@@ -53,6 +55,23 @@ func Marshal(v any) ([]byte, error) {
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) {
valueType := value.Type()
@@ -121,7 +140,7 @@ func appendValue(b []byte, tag byte, value reflect.Value) ([]byte, error) {
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
if i > 0 {
b = append(b, 0, 0)
b = append(b, separator, 0)
}
if b, err = appendValue(b, tag, value.Index(i)); err != nil {
return nil, err
@@ -179,32 +198,37 @@ func Unmarshal(data []byte, v any) error {
kind = value.Kind()
}
if kind != reflect.Struct {
return errors.New("tlv8: not implemented: " + kind.String())
}
switch kind {
case reflect.Slice:
return unmarshalSlice(data, value)
case reflect.Struct:
return unmarshalStruct(data, value)
}
func unmarshalStruct(b []byte, value reflect.Value) error {
var waitSlice bool
return errors.New("tlv8: not implemented: " + kind.String())
}
// unmarshalTLV can return two types of errors:
// - 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]
l := int(b[1])
// array item divider
if t == 0 && l == 0 {
b = b[2:]
waitSlice = true
continue
// array item divider (t == 0x00 || t == 0xFF)
if l == 0 {
return b[2:], errors.New("tlv8: zero item")
}
var v []byte
for {
if len(b) < 2+l {
return errors.New("tlv8: wrong size: " + value.Type().Name())
return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
}
v = append(v, b[2:2+l]...)
@@ -222,21 +246,38 @@ func unmarshalStruct(b []byte, value reflect.Value) error {
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
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
}
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
}

View File

@@ -2,6 +2,7 @@ package tlv8
import (
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/require"
@@ -107,3 +108,49 @@ func TestInterface(t *testing.T) {
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)
}