Files
2026-04-04 19:36:14 +02:00

265 lines
5.0 KiB
Go

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
}