mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-18 22:24:38 +08:00
Adds support HomeKit cameras!
This commit is contained in:
732
pkg/homekit/client.go
Normal file
732
pkg/homekit/client.go
Normal file
@@ -0,0 +1,732 @@
|
||||
package homekit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/AlexxIT/go2rtc/pkg/homekit/mdns"
|
||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||
"github.com/brutella/hap"
|
||||
"github.com/brutella/hap/chacha20poly1305"
|
||||
"github.com/brutella/hap/curve25519"
|
||||
"github.com/brutella/hap/ed25519"
|
||||
"github.com/brutella/hap/hkdf"
|
||||
"github.com/brutella/hap/tlv8"
|
||||
"github.com/tadglines/go-pkgs/crypto/srp"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Client for HomeKit. DevicePublic can be null.
|
||||
type Client struct {
|
||||
streamer.Element
|
||||
|
||||
DeviceAddress string // including port
|
||||
DeviceID string
|
||||
DevicePublic []byte
|
||||
ClientID string
|
||||
ClientPrivate []byte
|
||||
|
||||
OnEvent func(res *http.Response)
|
||||
Output func(msg interface{})
|
||||
|
||||
conn net.Conn
|
||||
secure *Secure
|
||||
httpResponse chan *bufio.Reader
|
||||
}
|
||||
|
||||
func NewClient(rawURL string) (*Client, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
c := &Client{
|
||||
DeviceAddress: u.Host,
|
||||
DeviceID: query.Get("device_id"),
|
||||
DevicePublic: DecodeKey(query.Get("device_public")),
|
||||
ClientID: query.Get("client_id"),
|
||||
ClientPrivate: DecodeKey(query.Get("client_private")),
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func Pair(deviceID, pin string) (*Client, error) {
|
||||
entry := mdns.GetEntry(deviceID)
|
||||
if entry == nil {
|
||||
return nil, errors.New("can't find device via mDNS")
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
DeviceAddress: fmt.Sprintf("%s:%d", entry.AddrV4.String(), entry.Port),
|
||||
DeviceID: deviceID,
|
||||
ClientID: GenerateUUID(),
|
||||
ClientPrivate: GenerateKey(),
|
||||
}
|
||||
|
||||
var mfi bool
|
||||
for _, field := range entry.InfoFields {
|
||||
if field[:2] == "ff" {
|
||||
if field[3] == '1' {
|
||||
mfi = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c, c.Pair(mfi, pin)
|
||||
}
|
||||
|
||||
func (c *Client) ClientPublic() []byte {
|
||||
return c.ClientPrivate[32:]
|
||||
}
|
||||
|
||||
func (c *Client) URL() string {
|
||||
return fmt.Sprintf(
|
||||
"homekit://%s?device_id=%s&device_public=%16x&client_id=%s&client_private=%32x",
|
||||
c.DeviceAddress, c.DeviceID, c.DevicePublic, c.ClientID, c.ClientPrivate,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) DialAndServe() error {
|
||||
if err := c.Dial(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Handle()
|
||||
}
|
||||
|
||||
func (c *Client) Dial() error {
|
||||
// update device host before dial
|
||||
if host := mdns.GetAddress(c.DeviceID); host != "" {
|
||||
c.DeviceAddress = host
|
||||
}
|
||||
|
||||
var err error
|
||||
c.conn, err = net.Dial("tcp", c.DeviceAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP M1: send our session public to device
|
||||
sessionPublic, sessionPrivate := curve25519.GenerateKeyPair()
|
||||
|
||||
// 1. generate payload
|
||||
// important not include other fields
|
||||
requestM1 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
}{
|
||||
State: hap.M1,
|
||||
PublicKey: sessionPublic[:],
|
||||
}
|
||||
// 2. pack payload to TLV8
|
||||
buf, err := tlv8.Marshal(requestM1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. send request
|
||||
resp, err := c.Post(UriPairVerify, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP M2: unpack deviceID from response
|
||||
responseM2 := PairVerifyPayload{}
|
||||
if err = tlv8.UnmarshalReader(resp.Body, &responseM2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. generate session shared key
|
||||
var deviceSessionPublic [32]byte
|
||||
copy(deviceSessionPublic[:], responseM2.PublicKey)
|
||||
sessionShared := curve25519.SharedSecret(sessionPrivate, deviceSessionPublic)
|
||||
sessionKey, err := hkdf.Sha512(
|
||||
sessionShared[:], []byte("Pair-Verify-Encrypt-Salt"),
|
||||
[]byte("Pair-Verify-Encrypt-Info"),
|
||||
)
|
||||
|
||||
// 2. decrypt M2 response with session key
|
||||
msg := responseM2.EncryptedData[:len(responseM2.EncryptedData)-16]
|
||||
var mac [16]byte
|
||||
copy(mac[:], responseM2.EncryptedData[len(msg):]) // 16 byte (MAC)
|
||||
|
||||
buf, err = chacha20poly1305.DecryptAndVerify(
|
||||
sessionKey[:], []byte("PV-Msg02"), msg, mac, nil,
|
||||
)
|
||||
|
||||
// 3. unpack payload from TLV8
|
||||
payloadM2 := PairVerifyPayload{}
|
||||
if err = tlv8.Unmarshal(buf, &payloadM2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. verify signature for M2 response with device public
|
||||
// device session + device id + our session
|
||||
if c.DevicePublic != nil {
|
||||
buf = nil
|
||||
buf = append(buf, responseM2.PublicKey[:]...)
|
||||
buf = append(buf, []byte(payloadM2.Identifier)...)
|
||||
buf = append(buf, sessionPublic[:]...)
|
||||
if !ed25519.ValidateSignature(
|
||||
c.DevicePublic[:], buf, payloadM2.Signature,
|
||||
) {
|
||||
return errors.New("device public signature invalid")
|
||||
}
|
||||
}
|
||||
|
||||
// STEP M3: send our clientID to device
|
||||
// 1. generate signature with our private key
|
||||
// (our session + our ID + device session)
|
||||
buf = nil
|
||||
buf = append(buf, sessionPublic[:]...)
|
||||
buf = append(buf, []byte(c.ClientID)...)
|
||||
buf = append(buf, responseM2.PublicKey[:]...)
|
||||
signature, err := ed25519.Signature(c.ClientPrivate[:], buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. generate payload
|
||||
payloadM3 := struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
Signature []byte `tlv8:"10"`
|
||||
}{
|
||||
Identifier: c.ClientID,
|
||||
Signature: signature,
|
||||
}
|
||||
// 3. pack payload to TLV8
|
||||
buf, err = tlv8.Marshal(payloadM3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. encrypt payload with session key
|
||||
msg, mac, _ = chacha20poly1305.EncryptAndSeal(
|
||||
sessionKey[:], []byte("PV-Msg03"), buf, nil,
|
||||
)
|
||||
|
||||
// 4. generate request
|
||||
requestM3 := struct {
|
||||
EncryptedData []byte `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
State: hap.M3,
|
||||
EncryptedData: append(msg, mac[:]...),
|
||||
}
|
||||
// 5. pack payload to TLV8
|
||||
buf, err = tlv8.Marshal(requestM3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err = c.Post(UriPairVerify, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP M4. Read response
|
||||
responseM4 := PairVerifyPayload{}
|
||||
if err = tlv8.UnmarshalReader(resp.Body, &responseM4); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. check response state
|
||||
if responseM4.State != 4 || responseM4.Status != 0 {
|
||||
return fmt.Errorf("wrong M4 response: %+v", responseM4)
|
||||
}
|
||||
|
||||
c.secure, err = NewSecure(sessionShared, false)
|
||||
//c.secure.Buffer = bytes.NewBuffer(nil)
|
||||
c.secure.Conn = c.conn
|
||||
|
||||
c.httpResponse = make(chan *bufio.Reader, 10)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// https://github.com/apple/HomeKitADK/blob/master/HAP/HAPPairingPairSetup.c
|
||||
func (c *Client) Pair(mfi bool, pin string) (err error) {
|
||||
pin = strings.ReplaceAll(pin, "-", "")
|
||||
if len(pin) != 8 {
|
||||
return fmt.Errorf("wrong PIN format: %s", pin)
|
||||
}
|
||||
pin = pin[:3] + "-" + pin[3:5] + "-" + pin[5:]
|
||||
|
||||
c.conn, err = net.Dial("tcp", c.DeviceAddress)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M1. Generate request
|
||||
reqM1 := struct {
|
||||
Method byte `tlv8:"0"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
State: hap.M1,
|
||||
}
|
||||
if mfi {
|
||||
reqM1.Method = 1 // ff=1 => method=1, ff=2 => method=0
|
||||
}
|
||||
buf, err := tlv8.Marshal(reqM1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M1. Send request
|
||||
res, err := c.Post(UriPairSetup, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M2. Read response
|
||||
resM2 := struct {
|
||||
Salt []byte `tlv8:"2"`
|
||||
PublicKey []byte `tlv8:"3"` // server public key, aka session.B
|
||||
State byte `tlv8:"6"`
|
||||
Error byte `tlv8:"7"`
|
||||
}{}
|
||||
if err = tlv8.UnmarshalReader(res.Body, &resM2); err != nil {
|
||||
return
|
||||
}
|
||||
if resM2.State != 2 || resM2.Error > 0 {
|
||||
return fmt.Errorf("wrong M2: %+v", resM2)
|
||||
}
|
||||
|
||||
// STEP M3. Generate session using pin
|
||||
username := []byte("Pair-Setup")
|
||||
|
||||
SRP, err := srp.NewSRP(
|
||||
"rfc5054.3072", sha512.New, keyDerivativeFuncRFC2945(username),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
SRP.SaltLength = 16
|
||||
|
||||
// username: "Pair-Setup"
|
||||
// password: PIN (with dashes)
|
||||
session := SRP.NewClientSession(username, []byte(pin))
|
||||
sessionShared, err := session.ComputeKey(resM2.Salt, resM2.PublicKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M3. Generate request
|
||||
reqM3 := struct {
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
Proof []byte `tlv8:"4"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
PublicKey: session.GetA(), // client public key, aka session.A
|
||||
Proof: session.ComputeAuthenticator(),
|
||||
State: hap.M3,
|
||||
}
|
||||
buf, err = tlv8.Marshal(reqM3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP M3. Send request
|
||||
res, err = c.Post(UriPairSetup, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M4. Read response
|
||||
resM4 := struct {
|
||||
Proof []byte `tlv8:"4"` // server proof
|
||||
State byte `tlv8:"6"`
|
||||
Error byte `tlv8:"7"`
|
||||
}{}
|
||||
if err = tlv8.UnmarshalReader(res.Body, &resM4); err != nil {
|
||||
return
|
||||
}
|
||||
if resM4.Error == 2 {
|
||||
return fmt.Errorf("wrong PIN: %s", pin)
|
||||
}
|
||||
if resM4.State != 4 || resM4.Error > 0 {
|
||||
return fmt.Errorf("wrong M4: %+v", resM4)
|
||||
}
|
||||
|
||||
// STEP M4. Verify response
|
||||
if !session.VerifyServerAuthenticator(resM4.Proof) {
|
||||
return errors.New("verify server auth fail")
|
||||
}
|
||||
|
||||
// STEP M5. Generate signature
|
||||
saltKey, err := hkdf.Sha512(
|
||||
sessionShared, []byte("Pair-Setup-Controller-Sign-Salt"),
|
||||
[]byte("Pair-Setup-Controller-Sign-Info"),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf = nil
|
||||
buf = append(buf, saltKey[:]...)
|
||||
buf = append(buf, []byte(c.ClientID)...)
|
||||
buf = append(buf, c.ClientPublic()...)
|
||||
|
||||
signature, err := ed25519.Signature(c.ClientPrivate, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M5. Generate payload
|
||||
msgM5 := struct {
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
Signature []byte `tlv8:"10"`
|
||||
}{
|
||||
Identifier: c.ClientID,
|
||||
PublicKey: c.ClientPublic(),
|
||||
Signature: signature,
|
||||
}
|
||||
buf, err = tlv8.Marshal(msgM5)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M5. Encrypt payload
|
||||
sessionKey, err := hkdf.Sha512(
|
||||
sessionShared, []byte("Pair-Setup-Encrypt-Salt"),
|
||||
[]byte("Pair-Setup-Encrypt-Info"),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf, mac, _ := chacha20poly1305.EncryptAndSeal(
|
||||
sessionKey[:], []byte("PS-Msg05"), buf, nil,
|
||||
)
|
||||
|
||||
// STEP M5. Generate request
|
||||
reqM5 := struct {
|
||||
EncryptedData []byte `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
EncryptedData: append(buf, mac[:]...),
|
||||
State: hap.M5,
|
||||
}
|
||||
buf, err = tlv8.Marshal(reqM5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// STEP M5. Send request
|
||||
res, err = c.Post(UriPairSetup, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M6. Read response
|
||||
resM6 := struct {
|
||||
EncryptedData []byte `tlv8:"5"`
|
||||
State byte `tlv8:"6"`
|
||||
Error byte `tlv8:"7"`
|
||||
}{}
|
||||
if err = tlv8.UnmarshalReader(res.Body, &resM6); err != nil {
|
||||
return
|
||||
}
|
||||
if resM6.State != 6 || resM6.Error > 0 {
|
||||
return fmt.Errorf("wrong M6: %+v", resM2)
|
||||
}
|
||||
|
||||
// STEP M6. Decrypt payload
|
||||
msg := resM6.EncryptedData[:len(resM6.EncryptedData)-16]
|
||||
copy(mac[:], resM6.EncryptedData[len(msg):]) // 16 byte (MAC)
|
||||
|
||||
buf, err = chacha20poly1305.DecryptAndVerify(
|
||||
sessionKey[:], []byte("PS-Msg06"), msg, mac, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msgM6 := struct {
|
||||
Identifier []byte `tlv8:"1"`
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
Signature []byte `tlv8:"10"`
|
||||
}{}
|
||||
if err = tlv8.Unmarshal(buf, &msgM6); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// STEP M6. Verify payload
|
||||
if saltKey, err = hkdf.Sha512(
|
||||
sessionShared, []byte("Pair-Setup-Accessory-Sign-Salt"),
|
||||
[]byte("Pair-Setup-Accessory-Sign-Info"),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf = nil
|
||||
buf = append(buf, saltKey[:]...)
|
||||
buf = append(buf, msgM6.Identifier...)
|
||||
buf = append(buf, msgM6.PublicKey...)
|
||||
|
||||
if !ed25519.ValidateSignature(
|
||||
msgM6.PublicKey[:], buf, msgM6.Signature,
|
||||
) {
|
||||
return errors.New("wrong server signature")
|
||||
}
|
||||
|
||||
if c.DeviceID != string(msgM6.Identifier) {
|
||||
return fmt.Errorf("wrong Device ID: %s", msgM6.Identifier)
|
||||
}
|
||||
|
||||
c.DevicePublic = msgM6.PublicKey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
if c.conn == nil {
|
||||
return nil
|
||||
}
|
||||
conn := c.conn
|
||||
c.conn = nil
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func (c *Client) GetAccessories() ([]*Accessory, error) {
|
||||
res, err := c.Get("/accessories")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := Accessories{}
|
||||
if err = json.Unmarshal(data, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, accs := range p.Accessories {
|
||||
for _, serv := range accs.Services {
|
||||
for _, char := range serv.Characters {
|
||||
char.AID = accs.AID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p.Accessories, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCharacters(query string) ([]*Character, error) {
|
||||
res, err := c.Get("/characteristics?id=" + query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := Characters{}
|
||||
if err = json.Unmarshal(data, &ch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ch.Characters, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCharacter(char *Character) error {
|
||||
query := fmt.Sprintf("%d.%d", char.AID, char.IID)
|
||||
chars, err := c.GetCharacters(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
char.Value = chars[0].Value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PutCharacters(characters ...*Character) (err error) {
|
||||
for i, char := range characters {
|
||||
if char.Event != nil {
|
||||
char = &Character{AID: char.AID, IID: char.IID, Event: char.Event}
|
||||
} else {
|
||||
char = &Character{AID: char.AID, IID: char.IID, Value: char.Value}
|
||||
}
|
||||
characters[i] = char
|
||||
}
|
||||
var data []byte
|
||||
if data, err = json.Marshal(Characters{characters}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res *http.Response
|
||||
if res, err = c.Put("/characteristics", data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if res.StatusCode >= 400 {
|
||||
return errors.New("wrong response status")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) GetImage(width, height int) ([]byte, error) {
|
||||
res, err := c.Post(
|
||||
"/resource", []byte(fmt.Sprintf(
|
||||
`{"image-width":%d,"image-height":%d,"resource-type":"image","reason":0}`,
|
||||
width, height,
|
||||
)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(res.Body)
|
||||
}
|
||||
|
||||
//func (c *Client) onEventData(r io.Reader) error {
|
||||
// if c.OnEvent == nil {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// data, err := io.ReadAll(r)
|
||||
//
|
||||
// ch := Characters{}
|
||||
// if err = json.Unmarshal(data, &ch); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// c.OnEvent(ch.Characters)
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func (c *Client) ListPairings() error {
|
||||
pReq := struct {
|
||||
Method byte `tlv8:"0"`
|
||||
State byte `tlv8:"6"`
|
||||
}{
|
||||
Method: hap.MethodListPairings,
|
||||
State: hap.M1,
|
||||
}
|
||||
data, err := tlv8.Marshal(pReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.Post("/pairings", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(res.Body)
|
||||
// TODO: don't know how to fix array of items
|
||||
var pRes struct {
|
||||
State byte `tlv8:"6"`
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
Permission byte `tlv8:"11"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(data, &pRes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PairingsAdd(clientID string, clientPublic []byte, admin bool) error {
|
||||
pReq := struct {
|
||||
Method byte `tlv8:"0"`
|
||||
Identifier string `tlv8:"1"`
|
||||
PublicKey []byte `tlv8:"3"`
|
||||
State byte `tlv8:"6"`
|
||||
Permission byte `tlv8:"11"`
|
||||
}{
|
||||
Method: hap.MethodAddPairing,
|
||||
Identifier: clientID,
|
||||
PublicKey: clientPublic,
|
||||
State: hap.M1,
|
||||
Permission: hap.PermissionUser,
|
||||
}
|
||||
if admin {
|
||||
pReq.Permission = hap.PermissionAdmin
|
||||
}
|
||||
|
||||
data, err := tlv8.Marshal(pReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.Post("/pairings", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(res.Body)
|
||||
var pRes struct {
|
||||
State byte `tlv8:"6"`
|
||||
Unknown byte `tlv8:"7"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(data, &pRes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DeletePairing(id string) error {
|
||||
reqM1 := struct {
|
||||
State byte `tlv8:"6"`
|
||||
Method byte `tlv8:"0"`
|
||||
Identifier string `tlv8:"1"`
|
||||
}{
|
||||
State: hap.M1,
|
||||
Method: hap.MethodDeletePairing,
|
||||
Identifier: id,
|
||||
}
|
||||
data, err := tlv8.Marshal(reqM1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.Post("/pairings", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(res.Body)
|
||||
var resM2 struct {
|
||||
State byte `tlv8:"6"`
|
||||
}
|
||||
if err = tlv8.Unmarshal(data, &resM2); err != nil {
|
||||
return err
|
||||
}
|
||||
if resM2.State != hap.M2 {
|
||||
return errors.New("wrong state")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) LocalAddr() string {
|
||||
return c.conn.LocalAddr().String()
|
||||
}
|
||||
|
||||
func DecodeKey(s string) []byte {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
data, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
Reference in New Issue
Block a user