Skip to content

Commit 5b1da84

Browse files
committed
Add TUTK protocol to xiaomi source
1 parent 079d404 commit 5b1da84

File tree

6 files changed

+1041
-269
lines changed

6 files changed

+1041
-269
lines changed

internal/xiaomi/xiaomi.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ func getCameraURL(url *url.URL) (string, error) {
9292

9393
var v struct {
9494
Vendor struct {
95-
VendorID byte `json:"vendor"`
95+
ID byte `json:"vendor"`
96+
Params struct {
97+
UID string `json:"p2p_id"`
98+
} `json:"vendor_params"`
9699
} `json:"vendor"`
97100
PublicKey string `json:"public_key"`
98101
Sign string `json:"sign"`
@@ -105,7 +108,11 @@ func getCameraURL(url *url.URL) (string, error) {
105108
query.Set("client_private", hex.EncodeToString(clientPrivate))
106109
query.Set("device_public", v.PublicKey)
107110
query.Set("sign", v.Sign)
108-
query.Set("vendor", getVendorName(v.Vendor.VendorID))
111+
query.Set("vendor", getVendorName(v.Vendor.ID))
112+
113+
if v.Vendor.ID == 1 {
114+
query.Set("uid", v.Vendor.Params.UID)
115+
}
109116

110117
url.RawQuery = query.Encode()
111118
return url.String(), nil

pkg/xiaomi/cs2/conn.go

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
package cs2
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"io"
7+
"net"
8+
"sync"
9+
"sync/atomic"
10+
"time"
11+
)
12+
13+
func Dial(host string) (*Conn, error) {
14+
conn, err := net.ListenUDP("udp", nil)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
c := &Conn{
20+
conn: conn,
21+
addr: &net.UDPAddr{IP: net.ParseIP(host), Port: 32108},
22+
}
23+
24+
if err = c.handshake(); err != nil {
25+
_ = conn.Close()
26+
return nil, err
27+
}
28+
29+
c.rawCh0 = make(chan []byte, 10)
30+
c.rawCh2 = make(chan []byte, 100)
31+
32+
go c.worker()
33+
34+
return c, nil
35+
}
36+
37+
type Conn struct {
38+
conn *net.UDPConn
39+
addr *net.UDPAddr
40+
41+
err error
42+
seqCh0 uint16
43+
seqCh3 uint16
44+
rawCh0 chan []byte
45+
rawCh2 chan []byte
46+
47+
cmdMu sync.Mutex
48+
cmdAck func()
49+
}
50+
51+
const (
52+
magic = 0xF1
53+
magicDrw = 0xD1
54+
msgLanSearch = 0x30
55+
msgPunchPkt = 0x41
56+
msgP2PRdy = 0x42
57+
msgDrw = 0xD0
58+
msgDrwAck = 0xD1
59+
msgAlive = 0xE0
60+
)
61+
62+
func (c *Conn) handshake() error {
63+
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
64+
65+
buf, err := c.WriteAndWait([]byte{magic, msgLanSearch, 0, 0}, msgPunchPkt)
66+
if err != nil {
67+
return fmt.Errorf("%s: read punch: %w", "cs2", err)
68+
}
69+
70+
_, err = c.WriteAndWait(buf, msgP2PRdy)
71+
if err != nil {
72+
return fmt.Errorf("%s: read ready: %w", "cs2", err)
73+
}
74+
75+
_ = c.Write([]byte{magic, msgAlive, 0, 0})
76+
77+
_ = c.SetDeadline(time.Time{})
78+
79+
return nil
80+
}
81+
82+
func (c *Conn) worker() {
83+
defer func() {
84+
close(c.rawCh0)
85+
close(c.rawCh2)
86+
}()
87+
88+
chAck := make([]uint16, 4)
89+
buf := make([]byte, 1200)
90+
var ch2WaitSize int
91+
var ch2WaitData []byte
92+
93+
for {
94+
n, addr, err := c.conn.ReadFromUDP(buf)
95+
if err != nil {
96+
c.err = fmt.Errorf("%s: %w", "cs2", err)
97+
return
98+
}
99+
100+
if string(addr.IP) != string(c.addr.IP) || n < 8 || buf[0] != magic {
101+
continue // skip messages from another IP
102+
}
103+
104+
//log.Printf("<- %x", buf[:n])
105+
106+
switch buf[1] {
107+
case msgDrw:
108+
ch := buf[5]
109+
seqHI := buf[6]
110+
seqLO := buf[7]
111+
112+
if chAck[ch] != uint16(seqHI)<<8|uint16(seqLO) {
113+
continue
114+
}
115+
chAck[ch]++
116+
117+
ack := []byte{magic, msgDrwAck, 0, 6, magicDrw, ch, 0, 1, seqHI, seqLO}
118+
if _, err = c.conn.WriteToUDP(ack, c.addr); err != nil {
119+
return
120+
}
121+
122+
switch ch {
123+
case 0:
124+
select {
125+
case c.rawCh0 <- buf[12:]:
126+
default:
127+
}
128+
continue
129+
130+
case 2:
131+
ch2WaitData = append(ch2WaitData, buf[8:n]...)
132+
133+
for len(ch2WaitData) > 4 {
134+
if ch2WaitSize == 0 {
135+
ch2WaitSize = int(binary.BigEndian.Uint32(ch2WaitData))
136+
ch2WaitData = ch2WaitData[4:]
137+
}
138+
if ch2WaitSize <= len(ch2WaitData) {
139+
select {
140+
case c.rawCh2 <- ch2WaitData[:ch2WaitSize]:
141+
default:
142+
c.err = fmt.Errorf("%s: media queue is full", "cs2")
143+
return
144+
}
145+
146+
ch2WaitData = ch2WaitData[ch2WaitSize:]
147+
ch2WaitSize = 0
148+
} else {
149+
break
150+
}
151+
}
152+
continue
153+
}
154+
155+
case msgP2PRdy: // skip it
156+
continue
157+
case msgDrwAck:
158+
if c.cmdAck != nil {
159+
c.cmdAck()
160+
}
161+
continue
162+
}
163+
164+
fmt.Printf("%s: unknown msg: %x\n", "cs2", buf[:n])
165+
}
166+
}
167+
168+
func (c *Conn) Write(req []byte) error {
169+
//log.Printf("-> %x", req)
170+
_, err := c.conn.WriteToUDP(req, c.addr)
171+
return err
172+
}
173+
174+
func (c *Conn) WriteAndWait(req []byte, waitMsg uint8) ([]byte, error) {
175+
var t *time.Timer
176+
t = time.AfterFunc(1, func() {
177+
if err := c.Write(req); err == nil && t != nil {
178+
t.Reset(time.Second)
179+
}
180+
})
181+
defer t.Stop()
182+
183+
buf := make([]byte, 1200)
184+
185+
for {
186+
n, addr, err := c.conn.ReadFromUDP(buf)
187+
if err != nil {
188+
return nil, err
189+
}
190+
191+
if string(addr.IP) != string(c.addr.IP) || n < 16 {
192+
continue // skip messages from another IP
193+
}
194+
195+
if buf[0] == magic && buf[1] == waitMsg {
196+
c.addr.Port = addr.Port
197+
return buf[:n], nil
198+
}
199+
}
200+
}
201+
202+
func (c *Conn) RemoteAddr() net.Addr {
203+
return c.addr
204+
}
205+
206+
func (c *Conn) SetDeadline(t time.Time) error {
207+
return c.conn.SetDeadline(t)
208+
}
209+
210+
func (c *Conn) Close() error {
211+
return c.conn.Close()
212+
}
213+
214+
func (c *Conn) Error() error {
215+
if c.err != nil {
216+
return c.err
217+
}
218+
return io.EOF
219+
}
220+
221+
func (c *Conn) ReadCommand() (cmd uint16, data []byte, err error) {
222+
buf, ok := <-c.rawCh0
223+
if !ok {
224+
return 0, nil, c.Error()
225+
}
226+
cmd = binary.LittleEndian.Uint16(buf[:2])
227+
data = buf[4:]
228+
return
229+
}
230+
231+
func (c *Conn) WriteCommand(cmd uint16, data []byte) error {
232+
c.cmdMu.Lock()
233+
defer c.cmdMu.Unlock()
234+
235+
var repeat atomic.Int32
236+
repeat.Store(5)
237+
238+
timeout := time.NewTicker(time.Second)
239+
defer timeout.Stop()
240+
241+
c.cmdAck = func() {
242+
repeat.Store(0)
243+
timeout.Reset(1)
244+
}
245+
246+
req := marshalCmd(0, c.seqCh0, uint32(cmd), data)
247+
c.seqCh0++
248+
249+
for {
250+
if err := c.Write(req); err != nil {
251+
return err
252+
}
253+
<-timeout.C
254+
r := repeat.Add(-1)
255+
if r < 0 {
256+
return nil
257+
}
258+
if r == 0 {
259+
return fmt.Errorf("%s: can't send command %d", "cs2", cmd)
260+
}
261+
}
262+
}
263+
264+
func (c *Conn) ReadPacket() ([]byte, error) {
265+
data, ok := <-c.rawCh2
266+
if !ok {
267+
return nil, c.Error()
268+
}
269+
return data, nil
270+
}
271+
272+
func (c *Conn) WritePacket(data []byte) error {
273+
const offset = 12
274+
275+
n := uint32(len(data))
276+
req := make([]byte, n+offset)
277+
req[0] = magic
278+
req[1] = msgDrw
279+
binary.BigEndian.PutUint16(req[2:], uint16(n+8))
280+
281+
req[4] = magicDrw
282+
req[5] = 3 // channel
283+
binary.BigEndian.PutUint16(req[6:], c.seqCh3)
284+
c.seqCh3++
285+
binary.BigEndian.PutUint32(req[8:], n)
286+
copy(req[offset:], data)
287+
288+
return c.Write(req)
289+
}
290+
291+
func marshalCmd(channel byte, seq uint16, cmd uint32, payload []byte) []byte {
292+
size := len(payload)
293+
req := make([]byte, 4+4+4+4+size)
294+
295+
// 1. message header (4 bytes)
296+
req[0] = magic
297+
req[1] = msgDrw
298+
binary.BigEndian.PutUint16(req[2:], uint16(4+4+4+size))
299+
300+
// 2. drw? header (4 bytes)
301+
req[4] = magicDrw
302+
req[5] = channel
303+
binary.BigEndian.PutUint16(req[6:], seq)
304+
305+
// 3. payload size (4 bytes)
306+
binary.BigEndian.PutUint32(req[8:], uint32(4+size))
307+
308+
// 4. payload command (4 bytes)
309+
binary.BigEndian.PutUint32(req[12:], cmd)
310+
311+
// 5. payload
312+
copy(req[16:], payload)
313+
314+
return req
315+
}

0 commit comments

Comments
 (0)