Files
go2rtc/pkg/xiaomi/tutk/conn.go
T
2025-12-22 00:00:33 +03:00

458 lines
10 KiB
Go

package tutk
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
)
func Dial(host, uid string) (*Conn, error) {
conn, err := net.ListenUDP("udp", nil)
if err != nil {
return nil, err
}
c := &Conn{
conn: conn,
addr: &net.UDPAddr{IP: net.ParseIP(host), Port: 32761},
sid: genSID(),
}
if err = c.handshake([]byte(uid)); err != nil {
_ = c.Close()
return nil, err
}
c.rawCmd = make(chan []byte, 10)
c.rawPkt = make(chan []byte, 100)
go c.worker()
return c, nil
}
type Conn struct {
conn *net.UDPConn
addr *net.UDPAddr
sid []byte
err error
seqCh0 uint16
seqCmd uint16
rawCmd chan []byte
rawPkt chan []byte
cmdMu sync.Mutex
cmdAck func()
}
func (c *Conn) handshake(uid []byte) (err error) {
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
if _, err = c.WriteAndWait(
c.msgLanSearch(uid, 1), // 01062100
func(_, res []byte) bool {
return bytes.Index(res, uid) == 16 // 02061200
},
); err != nil {
return err
}
if err = c.Write(c.msgLanSearch(uid, 2)); err != nil {
return err
}
if _, err = c.WriteAndWait(
c.msgAvClientStartReq(), // 07042100 + 00000b00
func(req, res []byte) bool {
mid := req[48:52]
return bytes.Index(res, mid) == 48 // 08041200 + 00140800
},
); err != nil {
return err
}
_ = c.SetDeadline(time.Time{})
return nil
}
func (c *Conn) worker() {
defer func() {
close(c.rawCmd)
close(c.rawPkt)
}()
buf := make([]byte, 1200)
var waitSeq uint16
var waitSize uint32
var waitData []byte
for {
n, addr, err := c.conn.ReadFromUDP(buf)
if err != nil {
c.err = fmt.Errorf("%s: %w", "tutk", err)
return
}
if string(addr.IP) != string(c.addr.IP) || n < 16 {
continue // skip messages from another IP
}
b := ReverseTransCodePartial(buf[:n])
//log.Printf("<- %x", b)
if b[0] != 0x04 || b[1] != 0x02 {
continue
}
if len(b) == 24 {
_ = c.Write(msgAckPing(b))
continue
}
switch b[14] {
case 0:
switch string(b[28:30]) {
case "\x00\x12":
_ = c.Write(c.msgAckCh0Req0012(b))
continue
case "\x00\x70":
_ = c.Write(c.msgAckCh0Req0070(b))
select {
case c.rawCmd <- b[52:]:
default:
}
continue
case "\x00\x71":
if c.cmdAck != nil {
c.cmdAck()
}
continue
case "\x01\x03":
seq := binary.LittleEndian.Uint16(b[40:])
if seq != waitSeq {
waitSeq = 0 // data loss
continue
}
if seq == 0 {
waitSize = binary.LittleEndian.Uint32(b[36:]) + 32
}
waitData = append(waitData, b[52:]...)
if n := uint32(len(waitData)); n < waitSize {
waitSeq++
continue
} else if n > waitSize {
waitSeq = 0 // data loss
continue
}
// create a buffer for the header and collected data
packetData := make([]byte, waitSize)
// there's a header at the end - let's move it to the beginning
copy(packetData, waitData[waitSize-32:])
copy(packetData[32:], waitData)
select {
case c.rawPkt <- packetData:
default:
c.err = fmt.Errorf("%s: media queue is full", "tutk")
return
}
waitSeq = 0
waitData = waitData[:0]
continue
case "\x01\x04":
waitSize2 := binary.LittleEndian.Uint32(b[36:])
waitData2 := b[52:]
if uint32(len(waitData2)) != waitSize2 {
continue // shouldn't happened for audio
}
packetData := make([]byte, waitSize2)
copy(packetData, waitData2)
select {
case c.rawPkt <- packetData:
default:
c.err = fmt.Errorf("%s: media queue is full", "tutk")
return
}
continue
}
case 1:
switch string(b[28:30]) {
case "\x00\x00":
_ = c.Write(msgAckCh1Req0000(b))
continue
case "\x00\x07":
_ = c.Write(msgAckCh1Req0007(b))
continue
}
case 5:
if len(b) == 48 {
_ = c.Write(msgAckCh5(b))
continue
}
}
fmt.Printf("%s: unknown msg: %x\n", "tutk", buf[:n])
}
}
func (c *Conn) Write(req []byte) error {
//log.Printf("-> %x", req)
_, err := c.conn.WriteToUDP(TransCodePartial(req), c.addr)
return err
}
func (c *Conn) WriteAndWait(req []byte, ok func(req, res []byte) bool) ([]byte, error) {
var t *time.Timer
t = time.AfterFunc(1, func() {
if err := c.Write(req); err == nil && t != nil {
t.Reset(time.Second)
}
})
defer t.Stop()
buf := make([]byte, 1200)
for {
n, addr, err := c.conn.ReadFromUDP(buf)
if err != nil {
return nil, err
}
if string(addr.IP) != string(c.addr.IP) || n < 16 {
continue // skip messages from another IP
}
res := ReverseTransCodePartial(buf[:n])
//log.Printf("<- %x", b)
if ok(req, res) {
c.addr.Port = addr.Port
return res, nil
}
}
}
func (c *Conn) RemoteAddr() net.Addr {
return c.addr
}
func (c *Conn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *Conn) Close() error {
return c.conn.Close()
}
func (c *Conn) Error() error {
if c.err != nil {
return c.err
}
return io.EOF
}
func (c *Conn) ReadCommand() (cmd uint16, data []byte, err error) {
buf, ok := <-c.rawCmd
if !ok {
return 0, nil, c.Error()
}
cmd = binary.LittleEndian.Uint16(buf[:2])
data = buf[4:]
return
}
// WriteCommand will send a command every second five times
func (c *Conn) WriteCommand(cmd uint16, data []byte) error {
c.cmdMu.Lock()
defer c.cmdMu.Unlock()
var repeat atomic.Int32
repeat.Store(5)
timeout := time.NewTicker(time.Second)
defer timeout.Stop()
c.cmdAck = func() {
repeat.Store(0)
timeout.Reset(1)
}
req := c.msgAvSendIOCtrl(cmd, data)
for {
if err := c.Write(req); err != nil {
return err
}
<-timeout.C
r := repeat.Add(-1)
if r < 0 {
return nil
}
if r == 0 {
return fmt.Errorf("%s: can't send command %d", "tutk", cmd)
}
}
}
func (c *Conn) ReadPacket() ([]byte, error) {
buf, ok := <-c.rawPkt
if !ok {
return nil, c.Error()
}
return buf, nil
}
func (c *Conn) WritePacket(data []byte) error {
panic("not implemented")
}
func genSID() []byte {
b := make([]byte, 16)
_, _ = rand.Read(b[8:])
copy(b, b[8:10])
b[4] = 0x0c
return b
}
func (c *Conn) msgLanSearch(uid []byte, i byte) []byte {
const size = 68 // or 52 or 68 or 88
b := make([]byte, size)
copy(b, "\x04\x02\x0f\x02")
b[4] = size - 16
copy(b[8:], "\x01\x06\x21\x00")
copy(b[16:], uid)
copy(b[52:], "\x00\x03\x01\x02") // or 07000303 or 01010204
copy(b[56:], c.sid[8:])
b[64] = i
return b
}
func (c *Conn) msg(size uint16) []byte {
b := make([]byte, size)
copy(b, "\x04\x02\x19\x0a")
binary.LittleEndian.PutUint16(b[4:], size-16)
binary.LittleEndian.PutUint16(b[6:], c.seqCh0)
c.seqCh0++ // start from 0
copy(b[8:], "\x07\x04\x21\x00")
return b
}
func (c *Conn) msgAvClientStartReq() []byte {
const size = 586 // or 586 or 598
b := c.msg(size)
copy(b[12:], c.sid)
copy(b[28:], "\x00\x00\x08\x00") // or 00000400 or 00000b00
binary.LittleEndian.PutUint16(b[44:], size-52)
binary.LittleEndian.PutUint32(b[48:], uint32(time.Now().UnixMilli()))
copy(b[size-16:], "\x04\x00\x00\x00\xfb\x07\x1f\x00")
return b
}
func (c *Conn) msgAvSendIOCtrl(cmd uint16, msg []byte) []byte {
size := 52 + 4 + uint16(len(msg))
b := c.msg(size)
copy(b[12:], c.sid)
copy(b[28:], "\x00\x70\x08\x00") // or 00700400 or 00700b00
c.seqCmd++ // start from 1
binary.LittleEndian.PutUint16(b[32:], c.seqCmd)
binary.LittleEndian.PutUint16(b[44:], size-52)
//_, _ = rand.Read(b[48:52]) // mid
binary.LittleEndian.PutUint32(b[48:], uint32(time.Now().UnixMilli()))
binary.LittleEndian.PutUint16(b[52:], cmd)
copy(b[56:], msg)
return b
}
const version = 0x19
func msgAckPing(req []byte) []byte {
// <- [24] 0402120a 08000000 28041200 000000005b0d4202070aa8c0
// -> [24] 04021a0a 08000000 27042100 000000005b0d4202070aa8c0
req[2] = version
req[8] = 0x27
req[10] = 0x21
return req
}
func msgAck(req []byte, size byte) []byte {
// xxxx??xx ??00xxxx 07xx21xx ...
req[2] = version
req[4] = size - 16
req[5] = 0x00
req[8] = 0x07
req[10] = 0x21
return req[:size]
}
func (c *Conn) msgAckCh0Req0012(req []byte) []byte {
// <- [64] 0402120a 30000000 08041200 e6e8 0000 0c000000e6e839da66b0dc14 00120800000000000000000000000000 0c00 000000000000 020000000100000001000000
// -> [72] 0402190a 38000300 07042100 e6e8 0000 0c000000e6e839da66b0dc14 00130b00000000000000000000000000 1400 000000000000 0200000001000000010000000000000000000000
const size = 72
req = append(req, 0, 0, 0, 0, 0, 0, 0, 0)
binary.LittleEndian.PutUint16(req[6:], c.seqCh0) // channel sequence
c.seqCh0++
req[28] = 0x00 // command
req[29] = 0x13
req[44] = size - 52 // data size
req[45] = 0x00
return msgAck(req, size)
}
func (c *Conn) msgAckCh0Req0070(req []byte) []byte {
// <- [104] 0402120a 58000300 08041200 e6e8 0000 0c000000e6e839da66b0dc14 00700800010000000000000000000000 3400 00007625a02f ...
// -> [ 52] 0402190a 24000400 07042100 e6e8 0000 0c000000e6e839da66b0dc14 00710800010000000000000000000000 0000 00007625a02f
binary.LittleEndian.PutUint16(req[6:], c.seqCh0) // channel sequence
c.seqCh0++
req[28] = 0x00 // command
req[29] = 0x71
req[44] = 0x00 // data size
req[45] = 0x00
return msgAck(req, 52)
}
func msgAckCh1Req0000(req []byte) []byte {
// <- [590] 0402120a 3e020100 08041200 e6e8 0100 0c000000e6e839da66b0dc14 00000800000000000000000000000000 1a02 0000d9c0001b ...
// -> [ 84] 0402190a 44000000 07042100 e6e8 0100 0c000000e6e839da66b0dc14 00140b00000000000000000000000000 2000 0000d9c0001b ...
const size = 84
req[28] = 0x00 // command
req[29] = 0x14
req[44] = size - 52 // data size
req[45] = 0x00
copy(req[52:], req[len(req)-32:]) // size
return msgAck(req, size)
}
func msgAckCh1Req0007(req []byte) []byte {
// <- [64] 0402120a 30000300 08041200 e6e8 0100 0c000000e6e839da66b0dc14 00070800000000000000000000000000 0c00 000001000000 000000006f1ea02f00000000
// -> [56] 0402190a 28000200 07042100 e6e8 0100 0c000000e6e839da66b0dc14 010a0b00000000000000000000000000 0000 000001000000 00000000
req[28] = 0x01 // command
req[29] = 0x0a
req[44] = 0x00 // data size
req[45] = 0x00
return msgAck(req, 56)
}
func msgAckCh5(req []byte) []byte {
// <- [48] 0402120a 20000200 08041200 e6e8 0500 0c000000e6e839da66b0dc14 5a97c2f1010500000000000000000000 00a0 0000
// -> [48] 0402190a 20000200 07042100 e6e8 0500 0c000000e6e839da66b0dc14 5a97c2f1410500000000000000000000 00a0 0000
req[32] = 0x41
return msgAck(req, 48)
}