package tutk import ( "fmt" "io" "net" "sync" "sync/atomic" "time" ) func Dial(host, uid, username, password string) (*Conn, error) { addr, err := net.ResolveUDPAddr("udp", host) if err != nil { // Default port for listening incoming LAN connections. // Important. It's not using for real connection. addr = &net.UDPAddr{IP: net.ParseIP(host), Port: 32761} } udpConn, err := net.ListenUDP("udp", nil) if err != nil { return nil, err } c := &Conn{UDPConn: udpConn, addr: addr} sid := GenSessionID() _ = c.SetDeadline(time.Now().Add(5 * time.Second)) if addr.Port != 10001 { err = c.connectDirect(uid, sid) } else { err = c.connectRemote(uid, sid) } if err != nil { _ = c.Close() return nil, err } if c.ver[0] >= 25 { c.session = NewSession25(c, sid) } else { c.session = NewSession16(c, sid) } if err = c.clientStart(username, password); err != nil { _ = c.Close() return nil, err } go c.worker() return c, nil } type Conn struct { *net.UDPConn addr *net.UDPAddr session Session ver []byte err error cmdMu sync.Mutex cmdAck func() } // Read overwrite net.Conn func (c *Conn) Read(buf []byte) (n int, err error) { for { var addr *net.UDPAddr if n, addr, err = c.UDPConn.ReadFromUDP(buf); err != nil { return 0, err } if string(c.addr.IP) != string(addr.IP) || n < 16 { continue // skip messages from another IP } if c.addr.Port != addr.Port { c.addr.Port = addr.Port } ReverseTransCodePartial(buf, buf[:n]) //log.Printf("<- %x", buf[:n]) return n, nil } } // Write overwrite net.Conn func (c *Conn) Write(b []byte) (n int, err error) { //log.Printf("-> %x", b) return c.UDPConn.WriteToUDP(TransCodePartial(nil, b), c.addr) } // RemoteAddr overwrite net.Conn func (c *Conn) RemoteAddr() net.Addr { return c.addr } func (c *Conn) Protocol() string { return "tutk+udp" } func (c *Conn) Version() string { if len(c.ver) == 1 { return fmt.Sprintf("TUTK/%d", c.ver[0]) } return fmt.Sprintf("TUTK/%d SDK %d.%d.%d.%d", c.ver[0], c.ver[1], c.ver[2], c.ver[3], c.ver[4]) } func (c *Conn) ReadCommand() (ctrlType uint32, ctrlData []byte, err error) { return c.session.RecvIOCtrl() } func (c *Conn) WriteCommand(ctrlType uint32, ctrlData []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) } buf := c.session.SendIOCtrl(ctrlType, ctrlData) for { if err := c.session.SessionWrite(0, buf); 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", ctrlType) } } } func (c *Conn) ReadPacket() (hdr, payload []byte, err error) { return c.session.RecvFrameData() } func (c *Conn) WritePacket(hdr, payload []byte) error { buf := c.session.SendFrameData(hdr, payload) return c.session.SessionWrite(1, buf) } func (c *Conn) Error() error { if c.err != nil { return c.err } return io.EOF } func (c *Conn) worker() { defer c.session.Close() buf := make([]byte, 1200) for { n, err := c.Read(buf) if err != nil { c.err = fmt.Errorf("%s: %w", "tutk", err) return } switch c.handleMsg(buf[:n]) { case msgUnknown: fmt.Printf("tutk: unknown msg: %x\n", buf[:n]) case msgError: return case msgCommandAck: if c.cmdAck != nil { c.cmdAck() } } } } const ( msgUnknown = iota msgError msgPing msgUnknownPing msgClientStart msgClientStart2 msgClientStartAck2 msgCommand msgCommandAck msgCounters msgMediaChunk msgMediaFrame msgMediaReorder msgMediaLost msgCh5 msgUnknown0007 // time sync without data? msgUnknown0008 // time sync with data? msgUnknown0010 msgUnknown0013 msgUnknown0900 msgUnknown0a08 msgUnknownCh1c msgDafang0012 ) func (c *Conn) handleMsg(msg []byte) int { // 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 0x08: switch ch := msg[14]; ch { case 0, 1: return c.session.SessionRead(ch, msg[28:]) case 5: if len(msg) == 48 { _, _ = c.Write(msgAckCh5(msg)) return msgCh5 } case 0x1c: return msgUnknownCh1c } case 0x18: return msgUnknownPing case 0x28: if len(msg) == 24 { _, _ = c.Write(msgAckPing(msg)) return msgPing } } return msgUnknown } func msgAckPing(msg []byte) []byte { // <- [24] 0402120a 08000000 28041200 000000005b0d4202070aa8c0 // -> [24] 04021a0a 08000000 27042100 000000005b0d4202070aa8c0 msg[8] = 0x27 msg[10] = 0x21 return msg } func msgAckCh5(msg []byte) []byte { // <- [48] 0402190a 20000400 07042100 7ecc05000c0000007ecc93c456c2561f 5a97c2f101050000000000000000000000010000 // -> [48] 0402190a 20000400 08041200 7ecc05000c0000007ecc93c456c2561f 5a97c2f141050000000000000000000000010000 msg[8] = 0x07 msg[10] = 0x21 msg[32] = 0x41 return msg }