263 lines
4.5 KiB
Go
263 lines
4.5 KiB
Go
package tutk
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
func Dial(host, uid, model string) (*Conn, error) {
|
|
conn, err := net.ListenUDP("udp", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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)
|
|
|
|
go c.worker()
|
|
|
|
return c, nil
|
|
}
|
|
|
|
type Conn struct {
|
|
conn *net.UDPConn
|
|
addr *net.UDPAddr
|
|
sid []byte
|
|
|
|
err error
|
|
rawCmd chan []byte
|
|
rawPkt chan []byte
|
|
|
|
cmdMu sync.Mutex
|
|
cmdAck func()
|
|
|
|
seqSendCh0 uint16
|
|
seqSendCh1 uint16
|
|
|
|
seqSendCmd1 uint16
|
|
seqSendCmd2 uint16
|
|
seqSendCnt uint16
|
|
seqSendAud 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.msgConnectByUID(uid, 1),
|
|
func(_, res []byte) bool {
|
|
return bytes.Index(res, uid) == 16 // 02061200
|
|
},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = c.Write(c.msgConnectByUID(uid, 2)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err = c.WriteAndWait(
|
|
c.msgAvClientStart(),
|
|
func(req, res []byte) bool {
|
|
return bytes.Index(res, req[48:52]) == 48
|
|
},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
_ = c.SetDeadline(time.Time{})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Conn) worker() {
|
|
defer func() {
|
|
close(c.rawCmd)
|
|
close(c.rawPkt)
|
|
}()
|
|
|
|
buf := make([]byte, 1200)
|
|
|
|
for {
|
|
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
|
|
}
|
|
|
|
ReverseTransCodePartial(buf, buf[:n])
|
|
//log.Printf("<- %x", buf[:n])
|
|
return n, addr, nil
|
|
}
|
|
}
|
|
|
|
func (c *Conn) WriteAndWait(req []byte, ok func(req, res []byte) bool) ([]byte, error) {
|
|
var t *time.Timer
|
|
t = time.AfterFunc(1, func() {
|
|
if err := c.Write(req); err == nil && t != nil {
|
|
t.Reset(time.Second)
|
|
}
|
|
})
|
|
defer t.Stop()
|
|
|
|
buf := make([]byte, 1200)
|
|
|
|
for {
|
|
n, addr, err := c.ReadFromUDP(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ok(req, buf[:n]) {
|
|
c.addr.Port = addr.Port
|
|
return buf[:n], nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Conn) Protocol() string {
|
|
return "tutk+udp"
|
|
}
|
|
|
|
func (c *Conn) RemoteAddr() net.Addr {
|
|
return c.addr
|
|
}
|
|
|
|
func (c *Conn) SetDeadline(t time.Time) error {
|
|
return c.conn.SetDeadline(t)
|
|
}
|
|
|
|
func (c *Conn) Close() error {
|
|
return c.conn.Close()
|
|
}
|
|
|
|
func (c *Conn) Error() error {
|
|
if c.err != nil {
|
|
return c.err
|
|
}
|
|
return io.EOF
|
|
}
|
|
|
|
func (c *Conn) ReadCommand() (cmd uint16, data []byte, err error) {
|
|
buf, ok := <-c.rawCmd
|
|
if !ok {
|
|
return 0, nil, c.Error()
|
|
}
|
|
cmd = binary.LittleEndian.Uint16(buf[:2])
|
|
data = buf[4:]
|
|
return
|
|
}
|
|
|
|
// WriteCommand will send a command every second five times
|
|
func (c *Conn) WriteCommand(cmd uint16, data []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)
|
|
}
|
|
|
|
msg := c.msgCtrl(cmd, data)
|
|
|
|
for {
|
|
if err := c.WriteCh0(msg); 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", cmd)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Conn) ReadPacket() ([]byte, error) {
|
|
buf, ok := <-c.rawPkt
|
|
if !ok {
|
|
return nil, c.Error()
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func (c *Conn) WritePacket(data []byte) error {
|
|
return c.WriteCh1(c.oldMsgAud(data))
|
|
}
|
|
|
|
func genSID() []byte {
|
|
b := make([]byte, 16)
|
|
_, _ = rand.Read(b[8:])
|
|
copy(b, b[8:10])
|
|
b[4] = 0x0c
|
|
return b
|
|
}
|