Skip to content

Commit 3b976c6

Browse files
committed
Improve HomeKit TLV format parser
1 parent 4026932 commit 3b976c6

File tree

2 files changed

+126
-38
lines changed

2 files changed

+126
-38
lines changed

pkg/hap/tlv8/tlv8.go

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,32 @@ func Marshal(v any) ([]byte, error) {
4646
}
4747

4848
switch kind {
49+
case reflect.Slice:
50+
return appendSlice(nil, value)
4951
case reflect.Struct:
5052
return appendStruct(nil, value)
5153
}
5254

5355
return nil, errors.New("tlv8: not implemented: " + kind.String())
5456
}
5557

58+
// separator the most confusing meaning in the documentation.
59+
// It can have a value of 0x00 or 0xFF or even 0x05.
60+
const separator = 0xFF
61+
62+
func appendSlice(b []byte, value reflect.Value) ([]byte, error) {
63+
for i := 0; i < value.Len(); i++ {
64+
if i > 0 {
65+
b = append(b, separator, 0)
66+
}
67+
var err error
68+
if b, err = appendStruct(b, value.Index(i)); err != nil {
69+
return nil, err
70+
}
71+
}
72+
return b, nil
73+
}
74+
5675
func appendStruct(b []byte, value reflect.Value) ([]byte, error) {
5776
valueType := value.Type()
5877

@@ -121,7 +140,7 @@ func appendValue(b []byte, tag byte, value reflect.Value) ([]byte, error) {
121140
case reflect.Slice:
122141
for i := 0; i < value.Len(); i++ {
123142
if i > 0 {
124-
b = append(b, 0, 0)
143+
b = append(b, separator, 0)
125144
}
126145
if b, err = appendValue(b, tag, value.Index(i)); err != nil {
127146
return nil, err
@@ -179,64 +198,86 @@ func Unmarshal(data []byte, v any) error {
179198
kind = value.Kind()
180199
}
181200

182-
if kind != reflect.Struct {
183-
return errors.New("tlv8: not implemented: " + kind.String())
201+
switch kind {
202+
case reflect.Slice:
203+
return unmarshalSlice(data, value)
204+
case reflect.Struct:
205+
return unmarshalStruct(data, value)
184206
}
185207

186-
return unmarshalStruct(data, value)
208+
return errors.New("tlv8: not implemented: " + kind.String())
187209
}
188210

189-
func unmarshalStruct(b []byte, value reflect.Value) error {
190-
var waitSlice bool
211+
// unmarshalTLV can return two types of errors:
212+
// - critical and then the value of []byte will be nil
213+
// - not critical and then []byte will contain the value
214+
func unmarshalTLV(b []byte, value reflect.Value) ([]byte, error) {
215+
if len(b) < 2 {
216+
return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
217+
}
191218

192-
for len(b) >= 2 {
193-
t := b[0]
194-
l := int(b[1])
219+
t := b[0]
220+
l := int(b[1])
195221

196-
// array item divider
197-
if t == 0 && l == 0 {
198-
b = b[2:]
199-
waitSlice = true
200-
continue
222+
// array item divider (t == 0x00 || t == 0xFF)
223+
if l == 0 {
224+
return b[2:], errors.New("tlv8: zero item")
225+
}
226+
227+
var v []byte
228+
229+
for {
230+
if len(b) < 2+l {
231+
return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
201232
}
202233

203-
var v []byte
234+
v = append(v, b[2:2+l]...)
235+
b = b[2+l:]
204236

205-
for {
206-
if len(b) < 2+l {
207-
return errors.New("tlv8: wrong size: " + value.Type().Name())
208-
}
237+
// if size == 255 and same tag - continue read big payload
238+
if l < 255 || len(b) < 2 || b[0] != t {
239+
break
240+
}
209241

210-
v = append(v, b[2:2+l]...)
211-
b = b[2+l:]
242+
l = int(b[1])
243+
}
212244

213-
// if size == 255 and same tag - continue read big payload
214-
if l < 255 || len(b) < 2 || b[0] != t {
215-
break
216-
}
245+
tag := strconv.Itoa(int(t))
217246

218-
l = int(b[1])
219-
}
247+
valueField, ok := getStructField(value, tag)
248+
if !ok {
249+
return b, fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
250+
}
220251

221-
tag := strconv.Itoa(int(t))
252+
if err := unmarshalValue(v, valueField); err != nil {
253+
return nil, err
254+
}
222255

223-
valueField, ok := getStructField(value, tag)
224-
if !ok {
225-
return fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
226-
}
256+
return b, nil
257+
}
227258

228-
if waitSlice {
229-
if valueField.Kind() != reflect.Slice {
230-
return fmt.Errorf("tlv8: should be slice T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
259+
func unmarshalSlice(b []byte, value reflect.Value) error {
260+
valueIndex := value.Index(growSlice(value))
261+
for len(b) > 0 {
262+
var err error
263+
if b, err = unmarshalTLV(b, valueIndex); err != nil {
264+
if b != nil {
265+
valueIndex = value.Index(growSlice(value))
266+
continue
231267
}
232-
waitSlice = false
268+
return err
233269
}
270+
}
271+
return nil
272+
}
234273

235-
if err := unmarshalValue(v, valueField); err != nil {
274+
func unmarshalStruct(b []byte, value reflect.Value) error {
275+
for len(b) > 0 {
276+
var err error
277+
if b, err = unmarshalTLV(b, value); b == nil && err != nil {
236278
return err
237279
}
238280
}
239-
240281
return nil
241282
}
242283

pkg/hap/tlv8/tlv8_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tlv8
22

33
import (
44
"encoding/hex"
5+
"strings"
56
"testing"
67

78
"github.com/stretchr/testify/require"
@@ -107,3 +108,49 @@ func TestInterface(t *testing.T) {
107108

108109
require.Equal(t, src, dst)
109110
}
111+
112+
func TestSlice1(t *testing.T) {
113+
var v struct {
114+
VideoAttrs []struct {
115+
Width uint16 `tlv8:"1"`
116+
Height uint16 `tlv8:"2"`
117+
Framerate uint8 `tlv8:"3"`
118+
} `tlv8:"3"`
119+
}
120+
121+
s := `030b010280070202380403011e ff00 030b010200050202d00203011e`
122+
b1, err := hex.DecodeString(strings.ReplaceAll(s, " ", ""))
123+
require.NoError(t, err)
124+
125+
err = Unmarshal(b1, &v)
126+
require.NoError(t, err)
127+
128+
require.Len(t, v.VideoAttrs, 2)
129+
130+
b2, err := Marshal(v)
131+
require.NoError(t, err)
132+
133+
require.Equal(t, b1, b2)
134+
}
135+
136+
func TestSlice2(t *testing.T) {
137+
var v []struct {
138+
Width uint16 `tlv8:"1"`
139+
Height uint16 `tlv8:"2"`
140+
Framerate uint8 `tlv8:"3"`
141+
}
142+
143+
s := `010280070202380403011e ff00 010200050202d00203011e`
144+
b1, err := hex.DecodeString(strings.ReplaceAll(s, " ", ""))
145+
require.NoError(t, err)
146+
147+
err = Unmarshal(b1, &v)
148+
require.NoError(t, err)
149+
150+
require.Len(t, v, 2)
151+
152+
b2, err := Marshal(v)
153+
require.NoError(t, err)
154+
155+
require.Equal(t, b1, b2)
156+
}

0 commit comments

Comments
 (0)