316 lines
5.6 KiB
Go
316 lines
5.6 KiB
Go
package cs2
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
func Dial(host 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: 32108},
|
|
}
|
|
|
|
if err = c.handshake(); err != nil {
|
|
_ = conn.Close()
|
|
return nil, err
|
|
}
|
|
|
|
c.rawCh0 = make(chan []byte, 10)
|
|
c.rawCh2 = make(chan []byte, 100)
|
|
|
|
go c.worker()
|
|
|
|
return c, nil
|
|
}
|
|
|
|
type Conn struct {
|
|
conn *net.UDPConn
|
|
addr *net.UDPAddr
|
|
|
|
err error
|
|
seqCh0 uint16
|
|
seqCh3 uint16
|
|
rawCh0 chan []byte
|
|
rawCh2 chan []byte
|
|
|
|
cmdMu sync.Mutex
|
|
cmdAck func()
|
|
}
|
|
|
|
const (
|
|
magic = 0xF1
|
|
magicDrw = 0xD1
|
|
msgLanSearch = 0x30
|
|
msgPunchPkt = 0x41
|
|
msgP2PRdy = 0x42
|
|
msgDrw = 0xD0
|
|
msgDrwAck = 0xD1
|
|
msgAlive = 0xE0
|
|
)
|
|
|
|
func (c *Conn) handshake() error {
|
|
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
buf, err := c.WriteAndWait([]byte{magic, msgLanSearch, 0, 0}, msgPunchPkt)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: read punch: %w", "cs2", err)
|
|
}
|
|
|
|
_, err = c.WriteAndWait(buf, msgP2PRdy)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: read ready: %w", "cs2", err)
|
|
}
|
|
|
|
_ = c.Write([]byte{magic, msgAlive, 0, 0})
|
|
|
|
_ = c.SetDeadline(time.Time{})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Conn) worker() {
|
|
defer func() {
|
|
close(c.rawCh0)
|
|
close(c.rawCh2)
|
|
}()
|
|
|
|
chAck := make([]uint16, 4)
|
|
buf := make([]byte, 1200)
|
|
var ch2WaitSize int
|
|
var ch2WaitData []byte
|
|
|
|
for {
|
|
n, addr, err := c.conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
c.err = fmt.Errorf("%s: %w", "cs2", err)
|
|
return
|
|
}
|
|
|
|
if string(addr.IP) != string(c.addr.IP) || n < 8 || buf[0] != magic {
|
|
continue // skip messages from another IP
|
|
}
|
|
|
|
//log.Printf("<- %x", buf[:n])
|
|
|
|
switch buf[1] {
|
|
case msgDrw:
|
|
ch := buf[5]
|
|
seqHI := buf[6]
|
|
seqLO := buf[7]
|
|
|
|
if chAck[ch] != uint16(seqHI)<<8|uint16(seqLO) {
|
|
continue
|
|
}
|
|
chAck[ch]++
|
|
|
|
ack := []byte{magic, msgDrwAck, 0, 6, magicDrw, ch, 0, 1, seqHI, seqLO}
|
|
if _, err = c.conn.WriteToUDP(ack, c.addr); err != nil {
|
|
return
|
|
}
|
|
|
|
switch ch {
|
|
case 0:
|
|
select {
|
|
case c.rawCh0 <- buf[12:]:
|
|
default:
|
|
}
|
|
continue
|
|
|
|
case 2:
|
|
ch2WaitData = append(ch2WaitData, buf[8:n]...)
|
|
|
|
for len(ch2WaitData) > 4 {
|
|
if ch2WaitSize == 0 {
|
|
ch2WaitSize = int(binary.BigEndian.Uint32(ch2WaitData))
|
|
ch2WaitData = ch2WaitData[4:]
|
|
}
|
|
if ch2WaitSize <= len(ch2WaitData) {
|
|
select {
|
|
case c.rawCh2 <- ch2WaitData[:ch2WaitSize]:
|
|
default:
|
|
c.err = fmt.Errorf("%s: media queue is full", "cs2")
|
|
return
|
|
}
|
|
|
|
ch2WaitData = ch2WaitData[ch2WaitSize:]
|
|
ch2WaitSize = 0
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
case msgP2PRdy: // skip it
|
|
continue
|
|
case msgDrwAck:
|
|
if c.cmdAck != nil {
|
|
c.cmdAck()
|
|
}
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("%s: unknown msg: %x\n", "cs2", buf[:n])
|
|
}
|
|
}
|
|
|
|
func (c *Conn) Write(req []byte) error {
|
|
//log.Printf("-> %x", req)
|
|
_, err := c.conn.WriteToUDP(req, c.addr)
|
|
return err
|
|
}
|
|
|
|
func (c *Conn) WriteAndWait(req []byte, waitMsg uint8) ([]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.conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if string(addr.IP) != string(c.addr.IP) || n < 16 {
|
|
continue // skip messages from another IP
|
|
}
|
|
|
|
if buf[0] == magic && buf[1] == waitMsg {
|
|
c.addr.Port = addr.Port
|
|
return buf[:n], nil
|
|
}
|
|
}
|
|
}
|
|
|
|
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.rawCh0
|
|
if !ok {
|
|
return 0, nil, c.Error()
|
|
}
|
|
cmd = binary.LittleEndian.Uint16(buf[:2])
|
|
data = buf[4:]
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
req := marshalCmd(0, c.seqCh0, uint32(cmd), data)
|
|
c.seqCh0++
|
|
|
|
for {
|
|
if err := c.Write(req); 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", "cs2", cmd)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Conn) ReadPacket() ([]byte, error) {
|
|
data, ok := <-c.rawCh2
|
|
if !ok {
|
|
return nil, c.Error()
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (c *Conn) WritePacket(data []byte) error {
|
|
const offset = 12
|
|
|
|
n := uint32(len(data))
|
|
req := make([]byte, n+offset)
|
|
req[0] = magic
|
|
req[1] = msgDrw
|
|
binary.BigEndian.PutUint16(req[2:], uint16(n+8))
|
|
|
|
req[4] = magicDrw
|
|
req[5] = 3 // channel
|
|
binary.BigEndian.PutUint16(req[6:], c.seqCh3)
|
|
c.seqCh3++
|
|
binary.BigEndian.PutUint32(req[8:], n)
|
|
copy(req[offset:], data)
|
|
|
|
return c.Write(req)
|
|
}
|
|
|
|
func marshalCmd(channel byte, seq uint16, cmd uint32, payload []byte) []byte {
|
|
size := len(payload)
|
|
req := make([]byte, 4+4+4+4+size)
|
|
|
|
// 1. message header (4 bytes)
|
|
req[0] = magic
|
|
req[1] = msgDrw
|
|
binary.BigEndian.PutUint16(req[2:], uint16(4+4+4+size))
|
|
|
|
// 2. drw? header (4 bytes)
|
|
req[4] = magicDrw
|
|
req[5] = channel
|
|
binary.BigEndian.PutUint16(req[6:], seq)
|
|
|
|
// 3. payload size (4 bytes)
|
|
binary.BigEndian.PutUint32(req[8:], uint32(4+size))
|
|
|
|
// 4. payload command (4 bytes)
|
|
binary.BigEndian.PutUint32(req[12:], cmd)
|
|
|
|
// 5. payload
|
|
copy(req[16:], payload)
|
|
|
|
return req
|
|
}
|