install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package iot
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/mqtt"
|
||||
)
|
||||
|
||||
type Codec struct {
|
||||
mqtt *mqtt.Client
|
||||
|
||||
devTopic string
|
||||
devKey string
|
||||
|
||||
body json.RawMessage
|
||||
}
|
||||
|
||||
type dps struct {
|
||||
Dps struct {
|
||||
Req string `json:"101,omitempty"`
|
||||
Res string `json:"102,omitempty"`
|
||||
} `json:"dps"`
|
||||
T uint32 `json:"t"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
ID uint64 `json:"id"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (c *Codec) WriteRequest(r *rpc.Request, v any) error {
|
||||
if v == nil {
|
||||
v = "[]"
|
||||
}
|
||||
|
||||
ts := uint32(time.Now().Unix())
|
||||
msg := dps{T: ts}
|
||||
msg.Dps.Req = fmt.Sprintf(
|
||||
`{"id":%d,"method":"%s","params":%s}`, r.Seq, r.ServiceMethod, v,
|
||||
)
|
||||
|
||||
payload, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//log.Printf("[roborock] send: %s", payload)
|
||||
|
||||
payload = c.Encrypt(payload, ts, ts, ts)
|
||||
|
||||
return c.mqtt.Publish("rr/m/i/"+c.devTopic, payload)
|
||||
}
|
||||
|
||||
func (c *Codec) ReadResponseHeader(r *rpc.Response) error {
|
||||
for {
|
||||
// receive any message from MQTT
|
||||
_, payload, err := c.mqtt.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// skip if it is not PUBLISH message
|
||||
if payload == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// decrypt MQTT PUBLISH payload
|
||||
if payload, err = c.Decrypt(payload); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip if we can't decrypt this payload (ex. binary payload)
|
||||
if payload == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
//log.Printf("[roborock] recv %s", payload)
|
||||
|
||||
// get content from response payload:
|
||||
// {"t":1676871268,"dps":{"102":"{\"id\":315003,\"result\":[\"ok\"]}"}}
|
||||
var msg dps
|
||||
if err = json.Unmarshal(payload, &msg); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var res response
|
||||
if err = json.Unmarshal([]byte(msg.Dps.Res), &res); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
r.Seq = res.ID
|
||||
if res.Error.Code != 0 {
|
||||
r.Error = res.Error.Message
|
||||
} else {
|
||||
c.body = res.Result
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Codec) ReadResponseBody(v any) error {
|
||||
switch vv := v.(type) {
|
||||
case *[]byte:
|
||||
*vv = c.body
|
||||
case *string:
|
||||
*vv = string(c.body)
|
||||
case *bool:
|
||||
*vv = string(c.body) == `["ok"]`
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Codec) Close() error {
|
||||
return c.mqtt.Close()
|
||||
}
|
||||
|
||||
func Dial(rawURL string) (*rpc.Client, error) {
|
||||
link, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// dial to MQTT
|
||||
conn, err := net.DialTimeout("tcp", link.Host, time.Second*5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// process MQTT SSL
|
||||
conf := &tls.Config{ServerName: link.Hostname()}
|
||||
sconn := tls.Client(conn, conf)
|
||||
if err = sconn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := link.Query()
|
||||
|
||||
// send MQTT login
|
||||
uk := md5.Sum([]byte(query.Get("u") + ":" + query.Get("k")))
|
||||
sk := md5.Sum([]byte(query.Get("s") + ":" + query.Get("k")))
|
||||
user := hex.EncodeToString(uk[1:5])
|
||||
pass := hex.EncodeToString(sk[8:])
|
||||
|
||||
c := &Codec{
|
||||
mqtt: mqtt.NewClient(sconn),
|
||||
devKey: query.Get("key"),
|
||||
devTopic: query.Get("u") + "/" + user + "/" + query.Get("did"),
|
||||
}
|
||||
|
||||
if err = c.mqtt.Connect("com.roborock.smart:mbrriot", user, pass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// subscribe on device topic
|
||||
if err = c.mqtt.Subscribe("rr/m/o/" + c.devTopic); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rpc.NewClientWithCodec(c), nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package iot
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
// key - convert timestamp to key
|
||||
func (c *Codec) key(timestamp uint32) []byte {
|
||||
const salt = "TXdfu$jyZ#TZHsg4"
|
||||
key := md5.Sum([]byte(encodeTimestamp(timestamp) + c.devKey + salt))
|
||||
return key[:]
|
||||
}
|
||||
|
||||
func (c *Codec) Decrypt(cipherText []byte) ([]byte, error) {
|
||||
if len(cipherText) < 32 || string(cipherText[:3]) != "1.0" {
|
||||
return nil, errors.New("wrong message prefix")
|
||||
}
|
||||
|
||||
i := len(cipherText) - 4
|
||||
if binary.BigEndian.Uint32(cipherText[i:]) != crc32.ChecksumIEEE(cipherText[:i]) {
|
||||
return nil, errors.New("wrong message checksum")
|
||||
}
|
||||
|
||||
if proto := binary.BigEndian.Uint16(cipherText[15:]); proto != 102 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
timestamp := binary.BigEndian.Uint32(cipherText[11:])
|
||||
return decryptECB(cipherText[19:i], c.key(timestamp)), nil
|
||||
}
|
||||
|
||||
func (c *Codec) Encrypt(plainText []byte, seq, random, timestamp uint32) []byte {
|
||||
const proto = 101
|
||||
|
||||
cipherText := encryptECB(plainText, c.key(timestamp))
|
||||
|
||||
size := uint16(len(cipherText))
|
||||
|
||||
msg := make([]byte, 23+size)
|
||||
copy(msg, "1.0")
|
||||
binary.BigEndian.PutUint32(msg[3:], seq)
|
||||
binary.BigEndian.PutUint32(msg[7:], random)
|
||||
binary.BigEndian.PutUint32(msg[11:], timestamp)
|
||||
binary.BigEndian.PutUint16(msg[15:], proto)
|
||||
binary.BigEndian.PutUint16(msg[17:], size)
|
||||
copy(msg[19:], cipherText)
|
||||
|
||||
crc := crc32.ChecksumIEEE(msg[:19+size])
|
||||
|
||||
binary.BigEndian.PutUint32(msg[19+size:], crc)
|
||||
return msg
|
||||
}
|
||||
|
||||
func encodeTimestamp(i uint32) string {
|
||||
const hextable = "0123456789abcdef"
|
||||
b := []byte{
|
||||
hextable[i>>8&0xF], hextable[i>>4&0xF],
|
||||
hextable[i>>16&0xF], hextable[i&0xF],
|
||||
hextable[i>>24&0xF], hextable[i>>20&0xF],
|
||||
hextable[i>>28&0xF], hextable[i>>12&0xF],
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func pad(plainText []byte, blockSize int) []byte {
|
||||
b0 := byte(blockSize - len(plainText)%blockSize)
|
||||
for i := byte(0); i < b0; i++ {
|
||||
plainText = append(plainText, b0)
|
||||
}
|
||||
return plainText
|
||||
}
|
||||
|
||||
func unpad(paddedText []byte) []byte {
|
||||
padSize := int(paddedText[len(paddedText)-1])
|
||||
return paddedText[:len(paddedText)-padSize]
|
||||
}
|
||||
|
||||
func encryptECB(plainText, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
plainText = pad(plainText, blockSize)
|
||||
cipherText := plainText
|
||||
|
||||
for len(plainText) > 0 {
|
||||
block.Encrypt(plainText, plainText)
|
||||
plainText = plainText[blockSize:]
|
||||
}
|
||||
|
||||
return cipherText
|
||||
}
|
||||
|
||||
func decryptECB(cipherText, key []byte) []byte {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
blockSize := block.BlockSize()
|
||||
paddedText := cipherText
|
||||
|
||||
for len(cipherText) > 0 {
|
||||
block.Decrypt(cipherText, cipherText)
|
||||
cipherText = cipherText[blockSize:]
|
||||
}
|
||||
|
||||
return unpad(paddedText)
|
||||
}
|
||||
Reference in New Issue
Block a user