ab27a042c1
- Introduced HKSV configuration options in homekit.go, allowing for motion detection and doorbell features. - Implemented API endpoints for triggering motion detection and doorbell events. - Enhanced server.go to handle HKSV sessions and manage motion detection states. - Created new accessory types for HKSV and doorbell in accessory.go. - Added support for audio recording configurations in ch207.go. - Defined new services for motion detection and doorbell in services_hksv.go. - Implemented opack encoding/decoding for HDS protocol in opack.go and protocol.go. - Updated OpenAPI documentation to reflect new endpoints and features. - Extended schema.json to include HKSV configuration options.
417 lines
9.8 KiB
Go
417 lines
9.8 KiB
Go
package hds
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"math"
|
|
)
|
|
|
|
// opack tags
|
|
const (
|
|
opackTrue = 0x01
|
|
opackFalse = 0x02
|
|
opackTerminator = 0x03
|
|
opackNull = 0x04
|
|
opackIntNeg1 = 0x07
|
|
opackSmallInt0 = 0x08 // 0x08-0x2F = integers 0-39
|
|
opackSmallInt39 = 0x2F
|
|
opackInt8 = 0x30
|
|
opackInt16 = 0x31
|
|
opackInt32 = 0x32
|
|
opackInt64 = 0x33
|
|
opackFloat32 = 0x35
|
|
opackFloat64 = 0x36
|
|
opackStr0 = 0x40 // 0x40-0x60 = inline string, length 0-32
|
|
opackStr32 = 0x60
|
|
opackStrLen1 = 0x61
|
|
opackStrLen2 = 0x62
|
|
opackStrLen4 = 0x63
|
|
opackStrLen8 = 0x64
|
|
opackData0 = 0x70 // 0x70-0x90 = inline data, length 0-32
|
|
opackData32 = 0x90
|
|
opackDataLen1 = 0x91
|
|
opackDataLen2 = 0x92
|
|
opackDataLen4 = 0x93
|
|
opackDataLen8 = 0x94
|
|
opackArr0 = 0xD0 // 0xD0-0xDE = counted array, 0-14 elements
|
|
opackArr14 = 0xDE
|
|
opackArrTerm = 0xDF // terminated array
|
|
opackDict0 = 0xE0 // 0xE0-0xEE = counted dict, 0-14 pairs
|
|
opackDict14 = 0xEE
|
|
opackDictTerm = 0xEF // terminated dict
|
|
)
|
|
|
|
func OpackMarshal(v any) []byte {
|
|
var buf []byte
|
|
return opackEncode(buf, v)
|
|
}
|
|
|
|
func OpackUnmarshal(data []byte) (any, error) {
|
|
v, _, err := opackDecode(data)
|
|
return v, err
|
|
}
|
|
|
|
func opackEncode(buf []byte, v any) []byte {
|
|
switch v := v.(type) {
|
|
case nil:
|
|
return append(buf, opackNull)
|
|
case bool:
|
|
if v {
|
|
return append(buf, opackTrue)
|
|
}
|
|
return append(buf, opackFalse)
|
|
case int:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case int8:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case int16:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case int32:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case int64:
|
|
return opackEncodeInt(buf, v)
|
|
case uint:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case uint8:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case uint16:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case uint32:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case uint64:
|
|
return opackEncodeInt(buf, int64(v))
|
|
case float32:
|
|
buf = append(buf, opackFloat32)
|
|
b := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(b, math.Float32bits(v))
|
|
return append(buf, b...)
|
|
case float64:
|
|
buf = append(buf, opackFloat64)
|
|
b := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(b, math.Float64bits(v))
|
|
return append(buf, b...)
|
|
case string:
|
|
return opackEncodeString(buf, v)
|
|
case []byte:
|
|
return opackEncodeData(buf, v)
|
|
case []any:
|
|
return opackEncodeArray(buf, v)
|
|
case map[string]any:
|
|
return opackEncodeDict(buf, v)
|
|
default:
|
|
return append(buf, opackNull)
|
|
}
|
|
}
|
|
|
|
func opackEncodeInt(buf []byte, v int64) []byte {
|
|
if v == -1 {
|
|
return append(buf, opackIntNeg1)
|
|
}
|
|
if v >= 0 && v <= 39 {
|
|
return append(buf, byte(opackSmallInt0+v))
|
|
}
|
|
if v >= -128 && v <= 127 {
|
|
return append(buf, opackInt8, byte(v))
|
|
}
|
|
if v >= -32768 && v <= 32767 {
|
|
buf = append(buf, opackInt16)
|
|
b := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(b, uint16(v))
|
|
return append(buf, b...)
|
|
}
|
|
if v >= -2147483648 && v <= 2147483647 {
|
|
buf = append(buf, opackInt32)
|
|
b := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(b, uint32(v))
|
|
return append(buf, b...)
|
|
}
|
|
buf = append(buf, opackInt64)
|
|
b := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(b, uint64(v))
|
|
return append(buf, b...)
|
|
}
|
|
|
|
func opackEncodeString(buf []byte, s string) []byte {
|
|
n := len(s)
|
|
if n <= 32 {
|
|
buf = append(buf, byte(opackStr0+n))
|
|
} else if n <= 0xFF {
|
|
buf = append(buf, opackStrLen1, byte(n))
|
|
} else if n <= 0xFFFF {
|
|
buf = append(buf, opackStrLen2)
|
|
b := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(b, uint16(n))
|
|
buf = append(buf, b...)
|
|
} else {
|
|
buf = append(buf, opackStrLen4)
|
|
b := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(b, uint32(n))
|
|
buf = append(buf, b...)
|
|
}
|
|
return append(buf, s...)
|
|
}
|
|
|
|
func opackEncodeData(buf []byte, data []byte) []byte {
|
|
n := len(data)
|
|
if n <= 32 {
|
|
buf = append(buf, byte(opackData0+n))
|
|
} else if n <= 0xFF {
|
|
buf = append(buf, opackDataLen1, byte(n))
|
|
} else if n <= 0xFFFF {
|
|
buf = append(buf, opackDataLen2)
|
|
b := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(b, uint16(n))
|
|
buf = append(buf, b...)
|
|
} else {
|
|
buf = append(buf, opackDataLen4)
|
|
b := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(b, uint32(n))
|
|
buf = append(buf, b...)
|
|
}
|
|
return append(buf, data...)
|
|
}
|
|
|
|
func opackEncodeArray(buf []byte, arr []any) []byte {
|
|
n := len(arr)
|
|
if n <= 14 {
|
|
buf = append(buf, byte(opackArr0+n))
|
|
} else {
|
|
buf = append(buf, opackArrTerm)
|
|
}
|
|
for _, v := range arr {
|
|
buf = opackEncode(buf, v)
|
|
}
|
|
if n > 14 {
|
|
buf = append(buf, opackTerminator)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func opackEncodeDict(buf []byte, dict map[string]any) []byte {
|
|
n := len(dict)
|
|
if n <= 14 {
|
|
buf = append(buf, byte(opackDict0+n))
|
|
} else {
|
|
buf = append(buf, opackDictTerm)
|
|
}
|
|
for k, v := range dict {
|
|
buf = opackEncodeString(buf, k)
|
|
buf = opackEncode(buf, v)
|
|
}
|
|
if n > 14 {
|
|
buf = append(buf, opackTerminator)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
var errOpackTruncated = errors.New("opack: truncated data")
|
|
var errOpackInvalidTag = errors.New("opack: invalid tag")
|
|
|
|
func opackDecode(data []byte) (any, int, error) {
|
|
if len(data) == 0 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
|
|
tag := data[0]
|
|
off := 1
|
|
|
|
switch {
|
|
case tag == opackNull:
|
|
return nil, off, nil
|
|
case tag == opackTrue:
|
|
return true, off, nil
|
|
case tag == opackFalse:
|
|
return false, off, nil
|
|
case tag == opackTerminator:
|
|
return nil, off, nil
|
|
case tag == opackIntNeg1:
|
|
return int64(-1), off, nil
|
|
case tag >= opackSmallInt0 && tag <= opackSmallInt39:
|
|
return int64(tag - opackSmallInt0), off, nil
|
|
case tag == opackInt8:
|
|
if len(data) < 2 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
return int64(int8(data[1])), 2, nil
|
|
case tag == opackInt16:
|
|
if len(data) < 3 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v := int16(binary.LittleEndian.Uint16(data[1:3]))
|
|
return int64(v), 3, nil
|
|
case tag == opackInt32:
|
|
if len(data) < 5 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v := int32(binary.LittleEndian.Uint32(data[1:5]))
|
|
return int64(v), 5, nil
|
|
case tag == opackInt64:
|
|
if len(data) < 9 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v := int64(binary.LittleEndian.Uint64(data[1:9]))
|
|
return int64(v), 9, nil
|
|
case tag == opackFloat32:
|
|
if len(data) < 5 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v := math.Float32frombits(binary.LittleEndian.Uint32(data[1:5]))
|
|
return float64(v), 5, nil
|
|
case tag == opackFloat64:
|
|
if len(data) < 9 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v := math.Float64frombits(binary.LittleEndian.Uint64(data[1:9]))
|
|
return v, 9, nil
|
|
|
|
// Inline string (0-32 bytes)
|
|
case tag >= opackStr0 && tag <= opackStr32:
|
|
n := int(tag - opackStr0)
|
|
if len(data) < off+n {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
return string(data[off : off+n]), off + n, nil
|
|
|
|
// String with length prefix
|
|
case tag >= opackStrLen1 && tag <= opackStrLen4:
|
|
n, sz := opackReadLen(data[off:], tag-opackStrLen1+1)
|
|
if sz == 0 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
off += sz
|
|
if len(data) < off+n {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
return string(data[off : off+n]), off + n, nil
|
|
|
|
// Inline data (0-32 bytes)
|
|
case tag >= opackData0 && tag <= opackData32:
|
|
n := int(tag - opackData0)
|
|
if len(data) < off+n {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
b := make([]byte, n)
|
|
copy(b, data[off:off+n])
|
|
return b, off + n, nil
|
|
|
|
// Data with length prefix
|
|
case tag >= opackDataLen1 && tag <= opackDataLen4:
|
|
n, sz := opackReadLen(data[off:], tag-opackDataLen1+1)
|
|
if sz == 0 {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
off += sz
|
|
if len(data) < off+n {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
b := make([]byte, n)
|
|
copy(b, data[off:off+n])
|
|
return b, off + n, nil
|
|
|
|
// Counted array (0-14)
|
|
case tag >= opackArr0 && tag <= opackArr14:
|
|
count := int(tag - opackArr0)
|
|
return opackDecodeArray(data[off:], count, false)
|
|
|
|
// Terminated array
|
|
case tag == opackArrTerm:
|
|
return opackDecodeArray(data[off:], 0, true)
|
|
|
|
// Counted dict (0-14)
|
|
case tag >= opackDict0 && tag <= opackDict14:
|
|
count := int(tag - opackDict0)
|
|
return opackDecodeDict(data[off:], count, false)
|
|
|
|
// Terminated dict
|
|
case tag == opackDictTerm:
|
|
return opackDecodeDict(data[off:], 0, true)
|
|
}
|
|
|
|
return nil, 0, errOpackInvalidTag
|
|
}
|
|
|
|
// opackReadLen reads a length from data using the given byte count (1=1byte, 2=2bytes, 3=4bytes, 4=8bytes)
|
|
func opackReadLen(data []byte, lenBytes byte) (int, int) {
|
|
switch lenBytes {
|
|
case 1:
|
|
if len(data) < 1 {
|
|
return 0, 0
|
|
}
|
|
return int(data[0]), 1
|
|
case 2:
|
|
if len(data) < 2 {
|
|
return 0, 0
|
|
}
|
|
return int(binary.LittleEndian.Uint16(data[:2])), 2
|
|
case 3: // 4-byte length (tag offset 3 = 4 bytes)
|
|
if len(data) < 4 {
|
|
return 0, 0
|
|
}
|
|
return int(binary.LittleEndian.Uint32(data[:4])), 4
|
|
case 4: // 8-byte length
|
|
if len(data) < 8 {
|
|
return 0, 0
|
|
}
|
|
return int(binary.LittleEndian.Uint64(data[:8])), 8
|
|
}
|
|
return 0, 0
|
|
}
|
|
|
|
func opackDecodeArray(data []byte, count int, terminated bool) ([]any, int, error) {
|
|
var arr []any
|
|
off := 0
|
|
for i := 0; terminated || i < count; i++ {
|
|
if off >= len(data) {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
if terminated && data[off] == opackTerminator {
|
|
off++
|
|
break
|
|
}
|
|
v, n, err := opackDecode(data[off:])
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
arr = append(arr, v)
|
|
off += n
|
|
}
|
|
return arr, off + 1, nil // +1 for outer tag
|
|
}
|
|
|
|
func opackDecodeDict(data []byte, count int, terminated bool) (map[string]any, int, error) {
|
|
dict := make(map[string]any)
|
|
off := 0
|
|
for i := 0; terminated || i < count; i++ {
|
|
if off >= len(data) {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
if terminated && data[off] == opackTerminator {
|
|
off++
|
|
break
|
|
}
|
|
// key
|
|
k, n, err := opackDecode(data[off:])
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
off += n
|
|
|
|
key, ok := k.(string)
|
|
if !ok {
|
|
return nil, 0, errors.New("opack: dict key is not string")
|
|
}
|
|
|
|
// value
|
|
if off >= len(data) {
|
|
return nil, 0, errOpackTruncated
|
|
}
|
|
v, n2, err := opackDecode(data[off:])
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
off += n2
|
|
dict[key] = v
|
|
}
|
|
return dict, off + 1, nil // +1 for outer tag
|
|
}
|