diff --git a/pkg/xiaomi/miss/client.go b/pkg/xiaomi/miss/client.go index 470c0e0e..560f9f49 100644 --- a/pkg/xiaomi/miss/client.go +++ b/pkg/xiaomi/miss/client.go @@ -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) } diff --git a/pkg/xiaomi/producer.go b/pkg/xiaomi/producer.go index 9abf66b0..f2ce4eda 100644 --- a/pkg/xiaomi/producer.go +++ b/pkg/xiaomi/producer.go @@ -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 diff --git a/pkg/xiaomi/tutk/README.md b/pkg/xiaomi/tutk/README.md new file mode 100644 index 00000000..95773594 --- /dev/null +++ b/pkg/xiaomi/tutk/README.md @@ -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 +``` diff --git a/pkg/xiaomi/tutk/conn.go b/pkg/xiaomi/tutk/conn.go index f3005224..37489ffa 100644 --- a/pkg/xiaomi/tutk/conn.go +++ b/pkg/xiaomi/tutk/conn.go @@ -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) -} diff --git a/pkg/xiaomi/tutk/crypto.go b/pkg/xiaomi/tutk/crypto.go index c98fc092..feeb31f7 100644 --- a/pkg/xiaomi/tutk/crypto.go +++ b/pkg/xiaomi/tutk/crypto.go @@ -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] diff --git a/pkg/xiaomi/tutk/proto.go b/pkg/xiaomi/tutk/proto.go new file mode 100644 index 00000000..86cf95af --- /dev/null +++ b/pkg/xiaomi/tutk/proto.go @@ -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 +} diff --git a/pkg/xiaomi/tutk/proto_new.go b/pkg/xiaomi/tutk/proto_new.go new file mode 100644 index 00000000..8f84b91b --- /dev/null +++ b/pkg/xiaomi/tutk/proto_new.go @@ -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 +} diff --git a/pkg/xiaomi/tutk/proto_old.go b/pkg/xiaomi/tutk/proto_old.go new file mode 100644 index 00000000..7ac4f803 --- /dev/null +++ b/pkg/xiaomi/tutk/proto_old.go @@ -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 +}