Improve support tutk vendor for xiaomi source
This commit is contained in:
@@ -35,7 +35,7 @@ func Dial(rawURL string) (*Client, error) {
|
||||
case "cs2":
|
||||
c.conn, err = cs2.Dial(u.Host, query.Get("transport"))
|
||||
case "tutk":
|
||||
c.conn, err = tutk.Dial(u.Host, query.Get("uid"))
|
||||
c.conn, err = tutk.Dial(u.Host, query.Get("uid"), query.Get("model"))
|
||||
default:
|
||||
return nil, fmt.Errorf("miss: unsupported vendor %s", s)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func Dial(rawURL string) (core.Producer, error) {
|
||||
}
|
||||
|
||||
func probe(client *miss.Client, channel, quality, audio uint8) ([]*core.Media, error) {
|
||||
_ = client.SetDeadline(time.Now().Add(core.ProbeTimeout))
|
||||
_ = client.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
if err := client.VideoStart(channel, quality, audio&1); err != nil {
|
||||
return nil, err
|
||||
@@ -158,7 +158,7 @@ func (p *Producer) Start() error {
|
||||
var audioTS uint32
|
||||
|
||||
for {
|
||||
_ = p.client.SetDeadline(time.Now().Add(core.ConnDeadline))
|
||||
_ = p.client.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
pkt, err := p.client.ReadPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# TUTK
|
||||
|
||||
The most terrible protocol I have ever had to work with.
|
||||
|
||||
## Messages
|
||||
|
||||
Ping from camera (24b). The shortest message.
|
||||
|
||||
```
|
||||
off sample
|
||||
0 0402 tutk magic
|
||||
2 190a tutk version (120a, 190a...)
|
||||
4 0800 msg size = len(b)-16 = 24-16
|
||||
6 0000 channel seq (always 0 for ping)
|
||||
8 2804 msg type (2804 - ping from camera, 0804 - usual msg from camera)
|
||||
10 1200 direction (12 - from camera, 21 - from client)
|
||||
12 00000000 fixed
|
||||
16 7ecc93c4 random
|
||||
20 56c2561f random
|
||||
```
|
||||
|
||||
Usual msg from camera (52b + msg data).
|
||||
|
||||
```
|
||||
off sample
|
||||
12 e6e8 same bytes b[20:22]
|
||||
14 0000 channel (0, 1, 5)
|
||||
16 0c00 fixed
|
||||
18 0000 fixed
|
||||
20 e6e839da random session id
|
||||
24 66b0dc14 random session id
|
||||
28 0070 command
|
||||
30 0b00 version
|
||||
32 0100 command seq
|
||||
34 0000 ???
|
||||
36 00000000 ???
|
||||
40 00000000 ???
|
||||
44 e300 msg data size
|
||||
46 0000 ???
|
||||
48 8f15a02f random msg id
|
||||
52 ... msg data
|
||||
```
|
||||
|
||||
Message with media from camera.
|
||||
|
||||
```
|
||||
off sample
|
||||
28 0c00 command
|
||||
30 0b00 version
|
||||
32 7700 command seq
|
||||
34 0000 ??? data only for last message per pack (14/14)
|
||||
36 0200 pack seq, don't know how packs used
|
||||
38 0914 09/14 - message seq/messages per packs
|
||||
40 01000000 fixed
|
||||
42 0500 command 2
|
||||
44 3200 command 2 seq
|
||||
46 4f00 chunks count per this frame
|
||||
48 1b00 chunk seq, starts from 0 (wrong for last chunk)
|
||||
50 0004 frame data size
|
||||
52 c8f6 random msg id
|
||||
54 01000000 previous frame seq, starts from 0
|
||||
58 02000000 current frame seq, starts from 1
|
||||
```
|
||||
+63
-263
@@ -12,23 +12,33 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Dial(host, uid string) (*Conn, error) {
|
||||
func Dial(host, uid, model 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(),
|
||||
addr, err := net.ResolveUDPAddr("udp", host)
|
||||
if err != nil {
|
||||
addr = &net.UDPAddr{IP: net.ParseIP(host), Port: 32761}
|
||||
}
|
||||
|
||||
c := &Conn{conn: conn, addr: addr, sid: genSID()}
|
||||
|
||||
if err = c.handshake([]byte(uid)); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch model {
|
||||
case "isa.camera.df3":
|
||||
c.msgCtrl = c.oldMsgCtrl
|
||||
c.handleCh0 = c.oldHandlerCh0()
|
||||
default:
|
||||
c.msgCtrl = c.newMsgCtrl
|
||||
c.handleCh0 = c.newHandlerCh0()
|
||||
}
|
||||
|
||||
c.rawCmd = make(chan []byte, 10)
|
||||
c.rawPkt = make(chan []byte, 100)
|
||||
|
||||
@@ -43,20 +53,32 @@ type Conn struct {
|
||||
sid []byte
|
||||
|
||||
err error
|
||||
seqCh0 uint16
|
||||
seqCmd uint16
|
||||
rawCmd chan []byte
|
||||
rawPkt chan []byte
|
||||
|
||||
cmdMu sync.Mutex
|
||||
cmdAck func()
|
||||
|
||||
seqSendCh0 uint16
|
||||
seqSendCh1 uint16
|
||||
|
||||
seqSendCmd1 uint16
|
||||
seqSendCmd2 uint16
|
||||
seqSendCnt uint16
|
||||
|
||||
seqRecvPkt0 uint16
|
||||
seqRecvPkt1 uint16
|
||||
seqRecvCmd2 uint16
|
||||
|
||||
msgCtrl func(ctrlType uint16, ctrlData []byte) []byte
|
||||
handleCh0 func(cmd []byte) int8
|
||||
}
|
||||
|
||||
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
|
||||
c.msgConnectByUID(uid, 1),
|
||||
func(_, res []byte) bool {
|
||||
return bytes.Index(res, uid) == 16 // 02061200
|
||||
},
|
||||
@@ -64,15 +86,14 @@ func (c *Conn) handshake(uid []byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.Write(c.msgLanSearch(uid, 2)); err != nil {
|
||||
if err = c.Write(c.msgConnectByUID(uid, 2)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = c.WriteAndWait(
|
||||
c.msgAvClientStartReq(), // 07042100 + 00000b00
|
||||
c.msgAvClientStart(),
|
||||
func(req, res []byte) bool {
|
||||
mid := req[48:52]
|
||||
return bytes.Index(res, mid) == 48 // 08041200 + 00140800
|
||||
return bytes.Index(res, req[48:52]) == 48
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
@@ -90,135 +111,45 @@ func (c *Conn) worker() {
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1200)
|
||||
var waitSeq uint16
|
||||
var waitSize uint32
|
||||
var waitData []byte
|
||||
|
||||
for {
|
||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||
n, _, err := c.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
c.err = fmt.Errorf("%s: %w", "tutk", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.handleMsg(buf[:n]) <= 0 {
|
||||
if c.err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Printf("tutk: unknown msg: %x\n", buf[:n])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Write(buf []byte) error {
|
||||
//log.Printf("-> %x", buf)
|
||||
_, err := c.conn.WriteToUDP(TransCodePartial(nil, buf), c.addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) ReadFromUDP(buf []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
for {
|
||||
if n, addr, err = c.conn.ReadFromUDP(buf); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
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])
|
||||
ReverseTransCodePartial(buf, buf[:n])
|
||||
//log.Printf("<- %x", buf[:n])
|
||||
return n, addr, nil
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -231,20 +162,14 @@ func (c *Conn) WriteAndWait(req []byte, ok func(req, res []byte) bool) ([]byte,
|
||||
buf := make([]byte, 1200)
|
||||
|
||||
for {
|
||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||
n, addr, err := c.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) {
|
||||
if ok(req, buf[:n]) {
|
||||
c.addr.Port = addr.Port
|
||||
return res, nil
|
||||
return buf[:n], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,10 +223,10 @@ func (c *Conn) WriteCommand(cmd uint16, data []byte) error {
|
||||
timeout.Reset(1)
|
||||
}
|
||||
|
||||
req := c.msgAvSendIOCtrl(cmd, data)
|
||||
msg := c.msgCtrl(cmd, data)
|
||||
|
||||
for {
|
||||
if err := c.Write(req); err != nil {
|
||||
if err := c.WriteCh0(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
<-timeout.C
|
||||
@@ -334,128 +259,3 @@ func genSID() []byte {
|
||||
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)
|
||||
}
|
||||
|
||||
+13
-10
@@ -1,7 +1,6 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
@@ -9,10 +8,12 @@ import (
|
||||
// I'd like to say hello to Charlie. Your name is forever etched into the history of streaming software.
|
||||
const charlie = "Charlie is the designer of P2P!!"
|
||||
|
||||
func ReverseTransCodePartial(src []byte) []byte {
|
||||
func ReverseTransCodePartial(dst, src []byte) []byte {
|
||||
n := len(src)
|
||||
tmp := make([]byte, n)
|
||||
dst := bytes.Clone(src)
|
||||
if len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
src16 := src
|
||||
tmp16 := tmp
|
||||
@@ -24,7 +25,7 @@ func ReverseTransCodePartial(src []byte) []byte {
|
||||
binary.LittleEndian.PutUint32(tmp16[i:], bits.RotateLeft32(x, i+3))
|
||||
}
|
||||
|
||||
swap(tmp16, dst16, 16)
|
||||
swap(dst16, tmp16, 16)
|
||||
|
||||
for i := 0; i != 16; i++ {
|
||||
tmp16[i] = dst16[i] ^ charlie[i]
|
||||
@@ -40,7 +41,7 @@ func ReverseTransCodePartial(src []byte) []byte {
|
||||
src16 = src16[16:]
|
||||
}
|
||||
|
||||
swap(src16, tmp16, n)
|
||||
swap(tmp16, src16, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
dst16[i] = tmp16[i] ^ charlie[i]
|
||||
@@ -49,10 +50,12 @@ func ReverseTransCodePartial(src []byte) []byte {
|
||||
return dst
|
||||
}
|
||||
|
||||
func TransCodePartial(src []byte) []byte {
|
||||
func TransCodePartial(dst, src []byte) []byte {
|
||||
n := len(src)
|
||||
tmp := make([]byte, n)
|
||||
dst := bytes.Clone(src)
|
||||
if len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
src16 := src
|
||||
tmp16 := tmp
|
||||
@@ -68,7 +71,7 @@ func TransCodePartial(src []byte) []byte {
|
||||
dst16[i] = tmp16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
swap(dst16, tmp16, 16)
|
||||
swap(tmp16, dst16, 16)
|
||||
|
||||
for i := 0; i != 16; i += 4 {
|
||||
x := binary.LittleEndian.Uint32(tmp16[i:])
|
||||
@@ -84,12 +87,12 @@ func TransCodePartial(src []byte) []byte {
|
||||
tmp16[i] = src16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
swap(tmp16, dst16, n)
|
||||
swap(dst16, tmp16, n)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func swap(src, dst []byte, n int) {
|
||||
func swap(dst, src []byte, n int) {
|
||||
switch n {
|
||||
case 2:
|
||||
_, _ = src[1], dst[1]
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Conn) WriteCh0(msg []byte) error {
|
||||
binary.LittleEndian.PutUint16(msg[6:], c.seqSendCh0)
|
||||
c.seqSendCh0++
|
||||
return c.Write(msg)
|
||||
}
|
||||
|
||||
func (c *Conn) WriteCh1(msg []byte) error {
|
||||
binary.LittleEndian.PutUint16(msg[6:], c.seqSendCh1)
|
||||
c.seqSendCh1++
|
||||
msg[14] = 1 // channel
|
||||
return c.Write(msg)
|
||||
}
|
||||
|
||||
func (c *Conn) msgConnectByUID(uid []byte, i byte) []byte {
|
||||
const size = 68 // or 52 or 68 or 88
|
||||
b := make([]byte, size)
|
||||
copy(b, "\x04\x02\x19\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 // 1 or 2
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *Conn) msgAvClientStart() []byte {
|
||||
const size = 566 + 32
|
||||
msg := c.msg(size)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x00\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[16:], size-52)
|
||||
//cmd[18] = 1 // ???
|
||||
binary.LittleEndian.PutUint32(cmd[20:], uint32(time.Now().UnixMilli()))
|
||||
|
||||
// important values for some cameras (not for df3)
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, "Miss")
|
||||
copy(data[257:], "client")
|
||||
|
||||
// 0100000004000000fb071f000000000000000000000003000000000001000000
|
||||
cfg := msg[566:]
|
||||
cfg[0] = 0 // 0 - simple proto, 1 - complex proto with "0Cxx" commands
|
||||
cfg[4] = 4
|
||||
copy(cfg[8:], "\xfb\x07\x1f\x00")
|
||||
cfg[22] = 3
|
||||
cfg[28] = 1
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Conn) msg(size uint16) []byte {
|
||||
b := make([]byte, size)
|
||||
copy(b, "\x04\x02\x19\x0a")
|
||||
binary.LittleEndian.PutUint16(b[4:], size-16)
|
||||
copy(b[8:], "\x07\x04\x21\x00")
|
||||
copy(b[12:], c.sid)
|
||||
return b
|
||||
}
|
||||
|
||||
const (
|
||||
msgPing = iota + 1
|
||||
msgClientStart00
|
||||
msgClientStart20
|
||||
msgCommand
|
||||
msgCounters
|
||||
msgMediaChunk
|
||||
msgMediaFrame
|
||||
msgMediaLost
|
||||
msgCh5
|
||||
msgUnknown0010
|
||||
msgUnknown0a08
|
||||
msgDafang0012
|
||||
msgDafang0071
|
||||
)
|
||||
|
||||
// handleMsg will return parsed msg type or zero
|
||||
func (c *Conn) handleMsg(msg []byte) int8 {
|
||||
//log.Printf("<- %x", msg)
|
||||
// off sample
|
||||
// 0 0402 tutk magic
|
||||
// 2 120a tutk version (120a, 190a...)
|
||||
// 4 0800 msg size = len(b)-16
|
||||
// 6 0000 channel seq
|
||||
// 8 28041200 msg type
|
||||
// 14 0100 channel (not all msg)
|
||||
// 28 0700 msg data (not all msg)
|
||||
switch msg[8] {
|
||||
case 0x28:
|
||||
_ = c.Write(msgAckPing(msg))
|
||||
return msgPing
|
||||
case 0x08:
|
||||
switch ch := msg[14]; ch {
|
||||
case 0:
|
||||
return c.handleCh0(msg[28:])
|
||||
case 1:
|
||||
return c.handleCh1(msg[28:])
|
||||
case 5:
|
||||
return c.handleCh5(msg)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Conn) handleCh1(cmd []byte) int8 {
|
||||
switch cid := string(cmd[:2]); cid {
|
||||
case "\x00\x00":
|
||||
_ = c.WriteCh1(c.msgAck0000(cmd))
|
||||
return msgClientStart00
|
||||
case "\x00\x20":
|
||||
//_ = c.WriteCh1(c.msgAck0020(cmd))
|
||||
return msgClientStart20
|
||||
case "\x09\x00": // skip
|
||||
return msgCounters
|
||||
case "\x0a\x08":
|
||||
_ = c.WriteCh1(c.msgAck0A08(cmd))
|
||||
return msgUnknown0a08
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Conn) handleCh5(msg []byte) int8 {
|
||||
if len(msg) != 48 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// <- [48] 0402190a 20000400 07042100 7ecc05000c0000007ecc93c456c2561f 5a97c2f101050000000000000000000000010000
|
||||
// -> [48] 0402190a 20000400 08041200 7ecc05000c0000007ecc93c456c2561f 5a97c2f141050000000000000000000000010000
|
||||
copy(msg[8:], "\x07\x04\x21\x00")
|
||||
msg[32] = 0x41
|
||||
_ = c.Write(msg)
|
||||
return msgCh5
|
||||
}
|
||||
|
||||
const msgHhrSize = 28
|
||||
const cmdHdrSize = 24
|
||||
|
||||
func (c *Conn) msgAck0000(msg28 []byte) []byte {
|
||||
const cmdDataSize = 36
|
||||
|
||||
msg := c.msg(msgHhrSize + cmdHdrSize + cmdDataSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x14\x0b\x00")
|
||||
cmd[16] = cmdDataSize
|
||||
copy(cmd[20:], msg28[20:24]) // request id (random)
|
||||
|
||||
// It's better not to answer anything, so camera won't send anything to this channel.
|
||||
//data := cmd[cmdHdrSize:]
|
||||
//copy(data, msg28[len(msg28)-32:])
|
||||
return msg
|
||||
}
|
||||
|
||||
//func (c *Conn) msgAck0020(msg28 []byte) []byte {
|
||||
// const cmdDataSize = 36
|
||||
//
|
||||
// msg := c.msg(msgHhrSize + cmdHdrSize + cmdDataSize)
|
||||
//
|
||||
// cmd := msg[msgHhrSize:]
|
||||
// copy(cmd, "\x00\x14\x0b\x00")
|
||||
// cmd[16] = cmdDataSize
|
||||
// copy(cmd[20:], msg28[20:24]) // request id (random)
|
||||
//
|
||||
// data := cmd[cmdHdrSize:]
|
||||
// data[5] = 1
|
||||
// data[7] = 1
|
||||
// data[8] = 1
|
||||
// data[12] = 4
|
||||
// copy(data[16:], "\xfb\x07\x1f\x00")
|
||||
// data[30] = 3
|
||||
// data[32] = 1
|
||||
// return msg
|
||||
//}
|
||||
|
||||
func (c *Conn) msgAck0A08(msg28 []byte) []byte {
|
||||
msg := c.msg(48)
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x0b\x00\x0b\x00")
|
||||
copy(cmd[8:], msg28[8:10])
|
||||
return msg
|
||||
}
|
||||
|
||||
func msgAckPing(req []byte) []byte {
|
||||
// <- [24] 0402120a 08000000 28041200 000000005b0d4202070aa8c0
|
||||
// -> [24] 04021a0a 08000000 27042100 000000005b0d4202070aa8c0
|
||||
req[8] = 0x27
|
||||
req[10] = 0x21
|
||||
return req
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Conn) newMsgCtrl(ctrlType uint16, ctrlData []byte) []byte {
|
||||
size := msgHhrSize + 28 + 4 + uint16(len(ctrlData))
|
||||
msg := c.msg(size)
|
||||
|
||||
// 0 0070 command
|
||||
// 2 0b00 version
|
||||
// 4 1000 seq
|
||||
// 6 0076 ???
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x70\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[4:], c.seqSendCmd1)
|
||||
c.seqSendCmd1++
|
||||
|
||||
// 8 0070 command (second time)
|
||||
// 10 0300 seq
|
||||
// 12 0100 chunks count
|
||||
// 14 0000 chunk seq (starts from 0)
|
||||
// 16 5500 size
|
||||
// 18 0000 random msg id (always 0)
|
||||
// 20 03000000 seq (second time)
|
||||
// 24 00000000
|
||||
// 28 01010000 ctrlType
|
||||
cmd[9] = 0x70
|
||||
cmd[12] = 1
|
||||
binary.LittleEndian.PutUint16(cmd[16:], size-52)
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[10:], c.seqSendCmd2)
|
||||
binary.LittleEndian.PutUint16(cmd[20:], c.seqSendCmd2)
|
||||
c.seqSendCmd2++
|
||||
|
||||
data := cmd[28:]
|
||||
binary.LittleEndian.PutUint16(data, ctrlType)
|
||||
copy(data[4:], ctrlData)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Conn) newHandlerCh0() func(msg []byte) int8 {
|
||||
var waitData []byte
|
||||
var waitSeq uint16
|
||||
|
||||
return func(cmd []byte) int8 {
|
||||
switch cmd[0] {
|
||||
case 0x07, 0x05:
|
||||
flag := cmd[1]
|
||||
|
||||
var cmd2 []byte
|
||||
if flag&0b1000 == 0 {
|
||||
// off sample
|
||||
// 0 0700 command
|
||||
// 2 0b00 version
|
||||
// 4 2700 seq
|
||||
// 6 0000 ???
|
||||
// 8 0700 command (second time)
|
||||
// 10 1400 seq
|
||||
// 12 1300 chunks count per this frame
|
||||
// 14 1100 chunk seq, starts from 0 (0x20 for last chunk)
|
||||
// 16 0004 frame data size
|
||||
// 18 0000 random msg id (always 0)
|
||||
// 20 02000000 previous frame seq, starts from 0
|
||||
// 24 03000000 current frame seq, starts from 1
|
||||
cmd2 = cmd[8:]
|
||||
} else {
|
||||
// off sample
|
||||
// 0 070d0b00
|
||||
// 4 30000000
|
||||
// 8 5c965500 ???
|
||||
// 12 ffff0000 ???
|
||||
// 16 0701 fixed command
|
||||
// 18 190001002000a802000006000000070000000
|
||||
cmd2 = cmd[16:]
|
||||
}
|
||||
|
||||
seq := binary.LittleEndian.Uint16(cmd2[2:])
|
||||
|
||||
// Check if this is first chunk for frame.
|
||||
// Handle protocol bug "0x20 chunk seq for last chunk" and sometimes
|
||||
// "0x20 chunk seq for first chunk if only one chunk".
|
||||
if binary.LittleEndian.Uint16(cmd2[6:]) == 0 || binary.LittleEndian.Uint16(cmd2[4:]) == 1 {
|
||||
waitData = waitData[:0]
|
||||
waitSeq = seq
|
||||
} else if seq != waitSeq {
|
||||
return msgMediaLost
|
||||
}
|
||||
|
||||
if flag&0b0001 == 0 {
|
||||
waitData = append(waitData, cmd2[20:]...)
|
||||
waitSeq++
|
||||
return msgMediaChunk
|
||||
}
|
||||
|
||||
c.seqRecvPkt1 = seq
|
||||
_ = c.WriteCh0(c.msgAckCounters())
|
||||
|
||||
data := cmd2[20:]
|
||||
n := len(data) - 32
|
||||
waitData = append(waitData, data[:n]...)
|
||||
|
||||
packetData := make([]byte, 32+len(waitData))
|
||||
copy(packetData, data[n:])
|
||||
copy(packetData[32:], waitData)
|
||||
|
||||
select {
|
||||
case c.rawPkt <- packetData:
|
||||
default:
|
||||
c.err = fmt.Errorf("%s: media queue is full", "tutk")
|
||||
return -1
|
||||
}
|
||||
return msgMediaFrame
|
||||
|
||||
case 0x00:
|
||||
_ = c.WriteCh0(c.msgAckCounters())
|
||||
c.seqRecvCmd2 = binary.LittleEndian.Uint16(cmd[2:])
|
||||
|
||||
switch cmd[1] {
|
||||
case 0x10:
|
||||
return msgUnknown0010 // unknown
|
||||
case 0x70:
|
||||
select {
|
||||
case c.rawCmd <- cmd[28:]:
|
||||
default:
|
||||
}
|
||||
return msgCommand // cmd from camera
|
||||
}
|
||||
|
||||
case 0x09:
|
||||
// off sample
|
||||
// 0 09000b00 cmd1
|
||||
// 4 0d000000 seqCmd1
|
||||
// 12 0000 seqRecvCmd2
|
||||
seq := binary.LittleEndian.Uint16(cmd[12:])
|
||||
if c.seqSendCmd1 > seq {
|
||||
if c.cmdAck != nil {
|
||||
c.cmdAck()
|
||||
}
|
||||
}
|
||||
return msgCounters
|
||||
|
||||
case 0x0a:
|
||||
// seq sample
|
||||
// 0 0a080b00
|
||||
// 4 03000000
|
||||
// 8 e2043200
|
||||
// 12 01000000
|
||||
_ = c.WriteCh0(c.msgAck0A08(cmd))
|
||||
return msgUnknown0a08
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) msgAckCounters() []byte {
|
||||
msg := c.msg(msgHhrSize + cmdHdrSize)
|
||||
|
||||
// off sample
|
||||
// 0 09000b00 cmd1
|
||||
// 4 2700 seqCmd1
|
||||
// 6 0000
|
||||
// 8 1300 seqRecvPkt0
|
||||
// 10 2600 seqRecvPkt1
|
||||
// 12 0400 seqRecvCmd2
|
||||
// 14 00000000
|
||||
// 18 1400 seqSendCnt
|
||||
// 20 d91a random
|
||||
// 22 0000
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x09\x00\x0b\x00")
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[4:], c.seqSendCmd1)
|
||||
c.seqSendCmd1++
|
||||
|
||||
// seqRecvPkt0 stores previous value of seqRecvPkt1
|
||||
// don't understand why this needs
|
||||
binary.LittleEndian.PutUint16(cmd[8:], c.seqRecvPkt0)
|
||||
c.seqRecvPkt0 = c.seqRecvPkt1
|
||||
binary.LittleEndian.PutUint16(cmd[10:], c.seqRecvPkt1)
|
||||
binary.LittleEndian.PutUint16(cmd[12:], c.seqRecvCmd2)
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[18:], c.seqSendCnt)
|
||||
c.seqSendCnt++
|
||||
binary.LittleEndian.PutUint16(cmd[20:], uint16(time.Now().UnixNano()))
|
||||
return msg
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (c *Conn) oldMsgCtrl(ctrlType uint16, ctrlData []byte) []byte {
|
||||
size := msgHhrSize + cmdHdrSize + 4 + uint16(len(ctrlData))
|
||||
msg := c.msg(size)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x70\x0b\x00")
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[4:], c.seqSendCmd1)
|
||||
c.seqSendCmd1++
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[16:], size-52)
|
||||
//binary.LittleEndian.PutUint32(cmd[20:], uint32(time.Now().UnixMilli()))
|
||||
|
||||
data := cmd[cmdHdrSize:]
|
||||
binary.LittleEndian.PutUint16(data, ctrlType)
|
||||
copy(data[4:], ctrlData)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Conn) oldHandlerCh0() func([]byte) int8 {
|
||||
var waitSeq uint16
|
||||
var waitSize uint32
|
||||
var waitData []byte
|
||||
|
||||
return func(cmd []byte) int8 {
|
||||
// 0 01030800 command + version
|
||||
// 4 00000000 fixed
|
||||
// 8 ac880100 total size
|
||||
// 12 6200 chunk seq
|
||||
// 14 2000 ???
|
||||
// 16 cc00 size
|
||||
// 18 0000
|
||||
// 20 01000000 fixed
|
||||
|
||||
switch cmd[0] {
|
||||
case 0x01:
|
||||
switch cmd[1] {
|
||||
case 0x03:
|
||||
seq := binary.LittleEndian.Uint16(cmd[12:])
|
||||
if seq != waitSeq {
|
||||
waitSeq = 0
|
||||
return msgMediaLost
|
||||
}
|
||||
if seq == 0 {
|
||||
waitData = waitData[:0]
|
||||
waitSize = binary.LittleEndian.Uint32(cmd[8:]) + 32
|
||||
}
|
||||
|
||||
waitData = append(waitData, cmd[24:]...)
|
||||
if n := uint32(len(waitData)); n < waitSize {
|
||||
waitSeq++
|
||||
return msgMediaChunk
|
||||
} else if n > waitSize {
|
||||
waitSeq = 0
|
||||
return msgMediaLost
|
||||
}
|
||||
|
||||
// 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 -1
|
||||
}
|
||||
|
||||
waitSeq = 0
|
||||
return msgMediaFrame
|
||||
|
||||
case 0x04:
|
||||
waitSize2 := binary.LittleEndian.Uint32(cmd[8:])
|
||||
waitData2 := cmd[24:]
|
||||
|
||||
if uint32(len(waitData2)) != waitSize2 {
|
||||
return -1 // 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 -1
|
||||
}
|
||||
return msgMediaFrame
|
||||
}
|
||||
|
||||
case 0x00:
|
||||
switch cmd[1] {
|
||||
case 0x70:
|
||||
_ = c.WriteCh0(c.msgAck0070(cmd))
|
||||
select {
|
||||
case c.rawCmd <- cmd[24:]:
|
||||
default:
|
||||
}
|
||||
return msgCommand
|
||||
case 0x12:
|
||||
_ = c.WriteCh0(c.msgAck0012(cmd))
|
||||
return msgDafang0012
|
||||
case 0x71:
|
||||
if c.cmdAck != nil {
|
||||
c.cmdAck()
|
||||
}
|
||||
return msgDafang0071
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) msgAck0070(msg28 []byte) []byte {
|
||||
// <- [104] 0402120a 58000300 08041200 e6e8 0000 0c000000e6e839da66b0dc14 00700800010000000000000000000000 3400 00007625a02f ...
|
||||
// -> [ 52] 0402190a 24000400 07042100 e6e8 0000 0c000000e6e839da66b0dc14 00710800010000000000000000000000 0000 00007625a02f
|
||||
msg := c.msg(52)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x71\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[4:], c.seqSendCmd1)
|
||||
c.seqSendCmd1++
|
||||
copy(cmd[8:], msg28[8:10])
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Conn) msgAck0012(msg28 []byte) []byte {
|
||||
// <- [64] 0402120a 30000000 08041200 e6e800000c000000e6e839da66b0dc14 001208000000000000000000000000000c00000000000000 020000000100000001000000
|
||||
// -> [72] 0402190a 38000300 07042100 e6e800000c000000e6e839da66b0dc14 00130b000000000000000000000000001400000000000000 0200000001000000010000000000000000000000
|
||||
const size = 72
|
||||
msg := c.msg(size)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x13\x0b\x00")
|
||||
cmd[16] = size - 52 // data size
|
||||
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, msg28[cmdHdrSize:])
|
||||
|
||||
return msg
|
||||
}
|
||||
Reference in New Issue
Block a user