install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
package dtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/tutk"
|
||||
"github.com/pion/dtls/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
magicCC51 = "\x51\xcc" // (wyze specific?)
|
||||
sdkVersion42 = "\x01\x01\x02\x04" // 4.2.1.1
|
||||
sdkVersion43 = "\x00\x08\x03\x04" // 4.3.8.0
|
||||
)
|
||||
|
||||
const (
|
||||
cmdDiscoReq uint16 = 0x0601
|
||||
cmdDiscoRes uint16 = 0x0602
|
||||
cmdSessionReq uint16 = 0x0402
|
||||
cmdSessionRes uint16 = 0x0404
|
||||
cmdDataTX uint16 = 0x0407
|
||||
cmdDataRX uint16 = 0x0408
|
||||
cmdKeepaliveReq uint16 = 0x0427
|
||||
cmdKeepaliveRes uint16 = 0x0428
|
||||
|
||||
headerSize = 16
|
||||
discoBodySize = 72
|
||||
discoSize = headerSize + discoBodySize
|
||||
sessionBody = 36
|
||||
sessionSize = headerSize + sessionBody
|
||||
)
|
||||
|
||||
const (
|
||||
cmdDiscoCC51 uint16 = 0x1002
|
||||
cmdKeepaliveCC51 uint16 = 0x1202
|
||||
cmdDTLSCC51 uint16 = 0x1502
|
||||
payloadSizeCC51 uint16 = 0x0028
|
||||
packetSizeCC51 = 52
|
||||
headerSizeCC51 = 28
|
||||
authSizeCC51 = 20
|
||||
keepaliveSizeCC51 = 48
|
||||
)
|
||||
|
||||
const (
|
||||
magicAVLoginResp uint16 = 0x2100
|
||||
magicIOCtrl uint16 = 0x7000
|
||||
magicChannelMsg uint16 = 0x1000
|
||||
magicACK uint16 = 0x0009
|
||||
magicAVLogin1 uint16 = 0x0000
|
||||
magicAVLogin2 uint16 = 0x2000
|
||||
)
|
||||
|
||||
const (
|
||||
protoVersion uint16 = 0x000c
|
||||
defaultCaps uint32 = 0x001f07fb
|
||||
)
|
||||
|
||||
const (
|
||||
iotcChannelMain = 0 // Main AV (we = DTLS Client)
|
||||
iotcChannelBack = 1 // Backchannel (we = DTLS Server)
|
||||
)
|
||||
|
||||
type DTLSConn struct {
|
||||
conn *net.UDPConn
|
||||
addr *net.UDPAddr
|
||||
frames *tutk.FrameHandler
|
||||
err error
|
||||
verbose bool
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
mu sync.RWMutex
|
||||
|
||||
// DTLS
|
||||
clientConn *dtls.Conn
|
||||
serverConn *dtls.Conn
|
||||
clientBuf chan []byte
|
||||
serverBuf chan []byte
|
||||
rawCmd chan []byte
|
||||
|
||||
// Identity
|
||||
uid string
|
||||
authKey string
|
||||
enr string
|
||||
psk []byte
|
||||
|
||||
// Session
|
||||
sid []byte
|
||||
ticket uint16
|
||||
hasTwoWayStreaming bool
|
||||
|
||||
// Protocol
|
||||
isCC51 bool
|
||||
seq uint16
|
||||
seqCmd uint16
|
||||
avSeq uint32
|
||||
kaSeq uint32
|
||||
audioSeq uint32
|
||||
audioFrameNo uint32
|
||||
|
||||
// Ack
|
||||
ackFlags uint16
|
||||
rxSeqStart uint16
|
||||
rxSeqEnd uint16
|
||||
rxSeqInit bool
|
||||
cmdAck func()
|
||||
}
|
||||
|
||||
func DialDTLS(host string, port int, uid, authKey, enr string, verbose bool) (*DTLSConn, error) {
|
||||
udp, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = udp.SetReadBuffer(2 * 1024 * 1024)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
psk := DerivePSK(enr)
|
||||
|
||||
if port == 0 {
|
||||
port = 32761
|
||||
}
|
||||
|
||||
c := &DTLSConn{
|
||||
conn: udp,
|
||||
addr: &net.UDPAddr{IP: net.ParseIP(host), Port: port},
|
||||
uid: uid,
|
||||
authKey: authKey,
|
||||
enr: enr,
|
||||
psk: psk,
|
||||
verbose: verbose,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
rxSeqStart: 0xffff,
|
||||
rxSeqEnd: 0xffff,
|
||||
}
|
||||
|
||||
if err = c.discovery(); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.clientBuf = make(chan []byte, 64)
|
||||
c.serverBuf = make(chan []byte, 64)
|
||||
c.rawCmd = make(chan []byte, 16)
|
||||
c.frames = tutk.NewFrameHandler(c.verbose)
|
||||
|
||||
c.wg.Add(1)
|
||||
go c.reader()
|
||||
|
||||
if err = c.connect(); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.wg.Add(1)
|
||||
go c.worker()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *DTLSConn) AVClientStart(timeout time.Duration) error {
|
||||
randomID := tutk.GenSessionID()
|
||||
pkt1 := c.msgAVLogin(magicAVLogin1, 570, 0x0001, randomID)
|
||||
pkt2 := c.msgAVLogin(magicAVLogin2, 572, 0x0000, randomID)
|
||||
pkt2[20]++ // pkt2 has randomID incremented by 1
|
||||
|
||||
if _, err := c.clientConn.Write(pkt1); err != nil {
|
||||
return fmt.Errorf("av login 1 failed: %w", err)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if _, err := c.clientConn.Write(pkt2); err != nil {
|
||||
return fmt.Errorf("av login 2 failed: %w", err)
|
||||
}
|
||||
|
||||
// Wait for response
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case data, ok := <-c.rawCmd:
|
||||
if !ok {
|
||||
return io.EOF
|
||||
}
|
||||
if len(data) >= 32 && binary.LittleEndian.Uint16(data) == magicAVLoginResp {
|
||||
c.hasTwoWayStreaming = data[31] == 1
|
||||
|
||||
ack := c.msgACK()
|
||||
c.clientConn.Write(ack)
|
||||
|
||||
// Start ACK sender for continuous streaming
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
ackTicker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ackTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
case <-ackTicker.C:
|
||||
if c.clientConn != nil {
|
||||
ack := c.msgACK()
|
||||
c.clientConn.Write(ack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
case <-timer.C:
|
||||
return context.DeadlineExceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) AVServStart() error {
|
||||
conn, err := NewDTLSServer(c.ctx, iotcChannelBack, c.addr, c.WriteDTLS, c.serverBuf, c.psk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dtls: server handshake failed: %w", err)
|
||||
}
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[DTLS] Server handshake complete on channel %d\n", iotcChannelBack)
|
||||
fmt.Printf("[SERVER] Waiting for AV Login request from camera...\n")
|
||||
}
|
||||
|
||||
// Wait for AV Login request from camera
|
||||
buf := make([]byte, 1024)
|
||||
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
go conn.Close()
|
||||
return fmt.Errorf("read av login: %w", err)
|
||||
}
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[SERVER] AV Login request len=%d data:\n%s", n, hexDump(buf[:n]))
|
||||
}
|
||||
|
||||
if n < 24 {
|
||||
go conn.Close()
|
||||
return fmt.Errorf("av login too short: %d bytes", n)
|
||||
}
|
||||
|
||||
checksum := binary.LittleEndian.Uint32(buf[20:])
|
||||
resp := c.msgAVLoginResponse(checksum)
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[SERVER] Sending AV Login response: %d bytes\n", len(resp))
|
||||
}
|
||||
|
||||
if _, err = conn.Write(resp); err != nil {
|
||||
go conn.Close()
|
||||
return fmt.Errorf("write av login response: %w", err)
|
||||
}
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[SERVER] AV Login response sent, waiting for possible resend...\n")
|
||||
}
|
||||
|
||||
// Camera may resend, respond again
|
||||
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||
if n, _ = conn.Read(buf); n > 0 {
|
||||
if c.verbose {
|
||||
fmt.Printf("[SERVER] Received AV Login resend: %d bytes\n", n)
|
||||
}
|
||||
conn.Write(resp)
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[SERVER] AV Login complete, ready for two way streaming\n")
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.serverConn = conn
|
||||
c.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DTLSConn) AVServStop() error {
|
||||
c.mu.Lock()
|
||||
serverConn := c.serverConn
|
||||
c.serverConn = nil
|
||||
|
||||
// Reset audio TX state
|
||||
c.audioSeq = 0
|
||||
c.audioFrameNo = 0
|
||||
c.mu.Unlock()
|
||||
|
||||
if serverConn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
go serverConn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DTLSConn) AVRecvFrameData() (*tutk.Packet, error) {
|
||||
select {
|
||||
case pkt, ok := <-c.frames.Recv():
|
||||
if !ok {
|
||||
return nil, c.Error()
|
||||
}
|
||||
return pkt, nil
|
||||
case <-c.ctx.Done():
|
||||
return nil, c.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) AVSendAudioData(codec byte, payload []byte, timestampUS uint32, sampleRate uint32, channels uint8) error {
|
||||
c.mu.Lock()
|
||||
conn := c.serverConn
|
||||
if conn == nil {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("av server not ready")
|
||||
}
|
||||
|
||||
frame := c.msgAudioFrame(payload, timestampUS, codec, sampleRate, channels)
|
||||
|
||||
c.mu.Unlock()
|
||||
|
||||
n, err := conn.Write(frame)
|
||||
if c.verbose {
|
||||
if err != nil {
|
||||
fmt.Printf("[SERVER TX] DTLS Write ERROR: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("[SERVER TX] len=%d, data:\n%s", n, hexDump(frame))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DTLSConn) Write(data []byte) error {
|
||||
if c.isCC51 {
|
||||
_, err := c.conn.WriteToUDP(data, c.addr)
|
||||
return err
|
||||
}
|
||||
_, err := c.conn.WriteToUDP(tutk.TransCodeBlob(data), c.addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DTLSConn) WriteDTLS(payload []byte, channel byte) error {
|
||||
var frame []byte
|
||||
if c.isCC51 {
|
||||
frame = c.msgTxDataCC51(payload, channel)
|
||||
} else {
|
||||
frame = c.msgTxData(payload, channel)
|
||||
}
|
||||
|
||||
return c.Write(frame)
|
||||
}
|
||||
|
||||
func (c *DTLSConn) WriteIOCtrl(payload []byte) error {
|
||||
_, err := c.conn.Write(c.msgIOCtrl(payload))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DTLSConn) WriteAndWait(req []byte, ok func(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()
|
||||
|
||||
_ = c.conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
defer c.conn.SetDeadline(time.Time{})
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
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
|
||||
}
|
||||
|
||||
var res []byte
|
||||
if c.isCC51 {
|
||||
res = buf[:n]
|
||||
} else {
|
||||
res = tutk.ReverseTransCodeBlob(buf[:n])
|
||||
}
|
||||
|
||||
if ok(res) {
|
||||
c.addr.Port = addr.Port
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) WriteAndWaitIOCtrl(payload []byte, match func([]byte) bool, timeout time.Duration) ([]byte, error) {
|
||||
frame := c.msgIOCtrl(payload)
|
||||
var t *time.Timer
|
||||
t = time.AfterFunc(1, func() {
|
||||
c.mu.RLock()
|
||||
conn := c.clientConn
|
||||
c.mu.RUnlock()
|
||||
if conn != nil {
|
||||
if _, err := conn.Write(frame); err == nil && t != nil {
|
||||
t.Reset(time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
defer t.Stop()
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case data, ok := <-c.rawCmd:
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
ack := c.msgACK()
|
||||
c.clientConn.Write(ack)
|
||||
|
||||
if match(data) {
|
||||
return data, nil
|
||||
}
|
||||
case <-timer.C:
|
||||
return nil, fmt.Errorf("timeout waiting for response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) HasTwoWayStreaming() bool {
|
||||
return c.hasTwoWayStreaming
|
||||
}
|
||||
|
||||
func (c *DTLSConn) IsBackchannelReady() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.serverConn != nil
|
||||
}
|
||||
|
||||
func (c *DTLSConn) RemoteAddr() *net.UDPAddr {
|
||||
return c.addr
|
||||
}
|
||||
|
||||
func (c *DTLSConn) LocalAddr() *net.UDPAddr {
|
||||
return c.conn.LocalAddr().(*net.UDPAddr)
|
||||
}
|
||||
|
||||
func (c *DTLSConn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *DTLSConn) Close() error {
|
||||
c.cancel()
|
||||
|
||||
c.mu.Lock()
|
||||
if conn := c.serverConn; conn != nil {
|
||||
c.serverConn = nil
|
||||
go conn.Close()
|
||||
}
|
||||
if conn := c.clientConn; conn != nil {
|
||||
c.clientConn = nil
|
||||
go conn.Close()
|
||||
}
|
||||
if c.frames != nil {
|
||||
c.frames.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.wg.Wait()
|
||||
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *DTLSConn) Error() error {
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (c *DTLSConn) discovery() error {
|
||||
c.sid = tutk.GenSessionID()
|
||||
|
||||
pktIOTC := tutk.TransCodeBlob(c.msgDisco(1))
|
||||
pktCC51 := c.msgDiscoCC51(0, 0, false)
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
c.conn.WriteToUDP(pktIOTC, c.addr)
|
||||
c.conn.WriteToUDP(pktCC51, c.addr)
|
||||
|
||||
c.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !addr.IP.Equal(c.addr.IP) {
|
||||
continue
|
||||
}
|
||||
|
||||
// CC51 protocol
|
||||
if n >= packetSizeCC51 && string(buf[:2]) == magicCC51 {
|
||||
if binary.LittleEndian.Uint16(buf[4:]) == cmdDiscoCC51 {
|
||||
c.addr, c.isCC51, c.ticket = addr, true, binary.LittleEndian.Uint16(buf[14:])
|
||||
if n >= 24 {
|
||||
copy(c.sid, buf[16:24])
|
||||
}
|
||||
return c.discoDoneCC51()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// IOTC Protocol (Basis)
|
||||
data := tutk.ReverseTransCodeBlob(buf[:n])
|
||||
if len(data) >= 16 && binary.LittleEndian.Uint16(data[8:]) == cmdDiscoRes {
|
||||
c.addr, c.isCC51 = addr, false
|
||||
return c.discoDone()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("discovery timeout")
|
||||
}
|
||||
|
||||
func (c *DTLSConn) discoDone() error {
|
||||
c.Write(c.msgDisco(2))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, err := c.WriteAndWait(c.msgSession(), func(res []byte) bool {
|
||||
return len(res) >= 16 && binary.LittleEndian.Uint16(res[8:]) == cmdSessionRes
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DTLSConn) discoDoneCC51() error {
|
||||
_, err := c.WriteAndWait(c.msgDiscoCC51(2, c.ticket, false), func(res []byte) bool {
|
||||
if len(res) < packetSizeCC51 || string(res[:2]) != magicCC51 {
|
||||
return false
|
||||
}
|
||||
cmd := binary.LittleEndian.Uint16(res[4:])
|
||||
dir := binary.LittleEndian.Uint16(res[8:])
|
||||
seq := binary.LittleEndian.Uint16(res[12:])
|
||||
return cmd == cmdDiscoCC51 && dir == 0xFFFF && seq == 3
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DTLSConn) connect() error {
|
||||
conn, err := NewDTLSClient(c.ctx, iotcChannelMain, c.addr, c.WriteDTLS, c.clientBuf, c.psk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dtls: client handshake failed: %w", err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.clientConn = conn
|
||||
c.mu.Unlock()
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[DTLS] Client handshake complete on channel %d\n", iotcChannelMain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DTLSConn) worker() {
|
||||
defer c.wg.Done()
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
n, err := c.clientConn.Read(buf)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
|
||||
if n < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
data := buf[:n]
|
||||
magic := binary.LittleEndian.Uint16(data)
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("[DTLS RX] magic=0x%04x len=%d\n", magic, n)
|
||||
}
|
||||
|
||||
switch magic {
|
||||
case magicAVLoginResp:
|
||||
c.queue(c.rawCmd, data)
|
||||
|
||||
case magicIOCtrl, magicChannelMsg:
|
||||
c.queue(c.rawCmd, data)
|
||||
|
||||
case protoVersion:
|
||||
// Seq-Tracking
|
||||
if len(data) >= 8 {
|
||||
seq := binary.LittleEndian.Uint16(data[4:])
|
||||
if !c.rxSeqInit {
|
||||
c.rxSeqInit = true
|
||||
}
|
||||
if seq > c.rxSeqEnd || c.rxSeqEnd == 0xffff {
|
||||
c.rxSeqEnd = seq
|
||||
}
|
||||
}
|
||||
c.queue(c.rawCmd, data)
|
||||
|
||||
case magicACK:
|
||||
c.mu.RLock()
|
||||
ack := c.cmdAck
|
||||
c.mu.RUnlock()
|
||||
if ack != nil {
|
||||
ack()
|
||||
}
|
||||
|
||||
default:
|
||||
channel := data[0]
|
||||
if channel == tutk.ChannelAudio || channel == tutk.ChannelIVideo || channel == tutk.ChannelPVideo {
|
||||
c.frames.Handle(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) reader() {
|
||||
defer c.wg.Done()
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
c.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !addr.IP.Equal(c.addr.IP) {
|
||||
if c.verbose {
|
||||
fmt.Printf("Ignored packet from unknown IP: %s\n", addr.IP.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if addr.Port != c.addr.Port {
|
||||
c.addr.Port = addr.Port
|
||||
}
|
||||
|
||||
// CC51 Protocol
|
||||
if c.isCC51 && n >= 12 && string(buf[:2]) == magicCC51 {
|
||||
cmd := binary.LittleEndian.Uint16(buf[4:])
|
||||
switch cmd {
|
||||
case cmdKeepaliveCC51:
|
||||
if n >= keepaliveSizeCC51 {
|
||||
_ = c.Write(c.msgKeepaliveCC51())
|
||||
}
|
||||
case cmdDTLSCC51:
|
||||
if n >= headerSizeCC51+authSizeCC51 {
|
||||
ch := byte(binary.LittleEndian.Uint16(buf[12:]) >> 8)
|
||||
dtlsData := buf[headerSizeCC51 : n-authSizeCC51]
|
||||
switch ch {
|
||||
case iotcChannelMain:
|
||||
c.queue(c.clientBuf, dtlsData)
|
||||
case iotcChannelBack:
|
||||
c.queue(c.serverBuf, dtlsData)
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// IOTC Protocol (Basis)
|
||||
data := tutk.ReverseTransCodeBlob(buf[:n])
|
||||
if len(data) < 16 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch binary.LittleEndian.Uint16(data[8:]) {
|
||||
case cmdKeepaliveRes:
|
||||
if len(data) > 24 {
|
||||
_ = c.Write(c.msgKeepalive(data[16:]))
|
||||
}
|
||||
case cmdDataRX:
|
||||
if len(data) > 28 {
|
||||
ch := data[14]
|
||||
switch ch {
|
||||
case iotcChannelMain:
|
||||
c.queue(c.clientBuf, data[28:])
|
||||
case iotcChannelBack:
|
||||
c.queue(c.serverBuf, data[28:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) queue(ch chan []byte, data []byte) {
|
||||
b := make([]byte, len(data))
|
||||
copy(b, data)
|
||||
select {
|
||||
case ch <- b:
|
||||
default:
|
||||
select {
|
||||
case <-ch:
|
||||
default:
|
||||
}
|
||||
ch <- b
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgDisco(stage byte) []byte {
|
||||
b := make([]byte, discoSize)
|
||||
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
||||
binary.LittleEndian.PutUint16(b[4:], discoBodySize) // body size
|
||||
binary.LittleEndian.PutUint16(b[8:], cmdDiscoReq) // 0x0601
|
||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||
body := b[headerSize:]
|
||||
copy(body[:20], c.uid)
|
||||
copy(body[36:], sdkVersion42) // SDK 4.2.1.1
|
||||
copy(body[40:], c.sid)
|
||||
body[48] = stage
|
||||
if stage == 1 && len(c.authKey) > 0 {
|
||||
copy(body[58:], c.authKey)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgDiscoCC51(seq, ticket uint16, isResponse bool) []byte {
|
||||
b := make([]byte, packetSizeCC51)
|
||||
copy(b[:2], magicCC51)
|
||||
binary.LittleEndian.PutUint16(b[4:], cmdDiscoCC51) // 0x1002
|
||||
binary.LittleEndian.PutUint16(b[6:], payloadSizeCC51) // 40 bytes
|
||||
if isResponse {
|
||||
binary.LittleEndian.PutUint16(b[8:], 0xFFFF) // response
|
||||
}
|
||||
binary.LittleEndian.PutUint16(b[12:], seq)
|
||||
binary.LittleEndian.PutUint16(b[14:], ticket)
|
||||
copy(b[16:24], c.sid)
|
||||
copy(b[24:28], sdkVersion43) // SDK 4.3.8.0
|
||||
b[28] = 0x1d // unknown field (capability/build flag?)
|
||||
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||
h.Write(b[:32])
|
||||
copy(b[32:52], h.Sum(nil))
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgKeepaliveCC51() []byte {
|
||||
c.kaSeq += 2
|
||||
b := make([]byte, keepaliveSizeCC51)
|
||||
copy(b[:2], magicCC51)
|
||||
binary.LittleEndian.PutUint16(b[4:], cmdKeepaliveCC51) // 0x1202
|
||||
binary.LittleEndian.PutUint16(b[6:], 0x0024) // 36 bytes payload
|
||||
binary.LittleEndian.PutUint32(b[16:], c.kaSeq) // counter
|
||||
copy(b[20:28], c.sid) // session ID
|
||||
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||
h.Write(b[:28])
|
||||
copy(b[28:48], h.Sum(nil))
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgSession() []byte {
|
||||
b := make([]byte, sessionSize)
|
||||
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
||||
binary.LittleEndian.PutUint16(b[4:], sessionBody) // body size
|
||||
binary.LittleEndian.PutUint16(b[8:], cmdSessionReq) // 0x0402
|
||||
binary.LittleEndian.PutUint16(b[10:], 0x0033) // flags
|
||||
body := b[headerSize:]
|
||||
copy(body[:20], c.uid)
|
||||
copy(body[20:], c.sid)
|
||||
binary.LittleEndian.PutUint32(body[32:], uint32(time.Now().Unix()))
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgAVLogin(magic uint16, size int, flags uint16, randomID []byte) []byte {
|
||||
b := make([]byte, size)
|
||||
binary.LittleEndian.PutUint16(b, magic)
|
||||
binary.LittleEndian.PutUint16(b[2:], protoVersion)
|
||||
binary.LittleEndian.PutUint16(b[16:], uint16(size-24)) // payload size
|
||||
binary.LittleEndian.PutUint16(b[18:], flags)
|
||||
copy(b[20:], randomID[:4])
|
||||
copy(b[24:], "admin") // username
|
||||
copy(b[280:], c.enr) // password/ENR
|
||||
binary.LittleEndian.PutUint32(b[540:], 4) // security_mode ?
|
||||
binary.LittleEndian.PutUint32(b[552:], defaultCaps) // capabilities
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgAVLoginResponse(checksum uint32) []byte {
|
||||
b := make([]byte, 60)
|
||||
binary.LittleEndian.PutUint16(b, 0x2100) // magic
|
||||
binary.LittleEndian.PutUint16(b[2:], 0x000c) // version
|
||||
b[4] = 0x10 // success
|
||||
binary.LittleEndian.PutUint32(b[16:], 0x24) // payload size
|
||||
binary.LittleEndian.PutUint32(b[20:], checksum) // echo checksum
|
||||
b[29] = 0x01 // enable flag
|
||||
b[31] = 0x01 // two-way streaming
|
||||
binary.LittleEndian.PutUint32(b[36:], 0x04) // buffer config
|
||||
binary.LittleEndian.PutUint32(b[40:], defaultCaps)
|
||||
binary.LittleEndian.PutUint16(b[54:], 0x0003) // channel info
|
||||
binary.LittleEndian.PutUint16(b[56:], 0x0002)
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgAudioFrame(payload []byte, timestampUS uint32, codec byte, sampleRate uint32, channels uint8) []byte {
|
||||
c.audioSeq++
|
||||
c.audioFrameNo++
|
||||
prevFrame := uint32(0)
|
||||
if c.audioFrameNo > 1 {
|
||||
prevFrame = c.audioFrameNo - 1
|
||||
}
|
||||
|
||||
totalPayload := len(payload) + 16 // payload + frameinfo
|
||||
b := make([]byte, 36+totalPayload)
|
||||
|
||||
// Outer header (36 bytes)
|
||||
b[0] = tutk.ChannelAudio // 0x03
|
||||
b[1] = tutk.FrameTypeStartAlt // 0x09
|
||||
binary.LittleEndian.PutUint16(b[2:], protoVersion)
|
||||
binary.LittleEndian.PutUint32(b[4:], c.audioSeq)
|
||||
binary.LittleEndian.PutUint32(b[8:], timestampUS)
|
||||
if c.audioFrameNo == 1 {
|
||||
binary.LittleEndian.PutUint32(b[12:], 0x00000001)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(b[12:], 0x00100001)
|
||||
}
|
||||
|
||||
// Inner header
|
||||
b[16] = tutk.ChannelAudio
|
||||
b[17] = tutk.FrameTypeEndSingle
|
||||
binary.LittleEndian.PutUint16(b[18:], uint16(prevFrame))
|
||||
binary.LittleEndian.PutUint16(b[20:], 0x0001) // pkt_total
|
||||
binary.LittleEndian.PutUint16(b[22:], 0x0010) // flags
|
||||
binary.LittleEndian.PutUint32(b[24:], uint32(totalPayload))
|
||||
binary.LittleEndian.PutUint32(b[28:], prevFrame)
|
||||
binary.LittleEndian.PutUint32(b[32:], c.audioFrameNo)
|
||||
copy(b[36:], payload) // Payload + FrameInfo
|
||||
fi := b[36+len(payload):]
|
||||
fi[0] = codec // Codec ID (low byte)
|
||||
fi[1] = 0 // Codec ID (high byte, unused)
|
||||
// Audio flags: [3:2]=sampleRateIdx [1]=16bit [0]=stereo
|
||||
srIdx := tutk.GetSampleRateIndex(sampleRate)
|
||||
fi[2] = (srIdx << 2) | 0x02 // 16-bit always set
|
||||
if channels == 2 {
|
||||
fi[2] |= 0x01
|
||||
}
|
||||
fi[4] = 1 // online
|
||||
binary.LittleEndian.PutUint32(fi[12:], (c.audioFrameNo-1)*tutk.GetSamplesPerFrame(codec)*1000/sampleRate)
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgTxData(payload []byte, channel byte) []byte {
|
||||
bodySize := 12 + len(payload)
|
||||
b := make([]byte, 16+bodySize)
|
||||
copy(b, "\x04\x02\x1a\x0b") // marker + mode=data
|
||||
binary.LittleEndian.PutUint16(b[4:], uint16(bodySize)) // body size
|
||||
binary.LittleEndian.PutUint16(b[6:], c.seq) // sequence
|
||||
c.seq++
|
||||
binary.LittleEndian.PutUint16(b[8:], cmdDataTX) // 0x0407
|
||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||
copy(b[12:], c.sid[:2]) // rid[0:2]
|
||||
b[14] = channel // channel
|
||||
b[15] = 0x01 // marker
|
||||
binary.LittleEndian.PutUint32(b[16:], 0x0000000c) // const
|
||||
copy(b[20:], c.sid[:8]) // rid
|
||||
copy(b[28:], payload)
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgTxDataCC51(payload []byte, channel byte) []byte {
|
||||
payloadSize := uint16(16 + len(payload) + authSizeCC51)
|
||||
b := make([]byte, headerSizeCC51+len(payload)+authSizeCC51)
|
||||
copy(b[:2], magicCC51)
|
||||
binary.LittleEndian.PutUint16(b[4:], cmdDTLSCC51) // 0x1502
|
||||
binary.LittleEndian.PutUint16(b[6:], payloadSize)
|
||||
binary.LittleEndian.PutUint16(b[12:], uint16(0x0010)|(uint16(channel)<<8)) // channel in high byte
|
||||
binary.LittleEndian.PutUint16(b[14:], c.ticket)
|
||||
copy(b[16:24], c.sid)
|
||||
binary.LittleEndian.PutUint32(b[24:], 1) // const
|
||||
copy(b[headerSizeCC51:], payload)
|
||||
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||
h.Write(b[:headerSizeCC51])
|
||||
copy(b[headerSizeCC51+len(payload):], h.Sum(nil))
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgACK() []byte {
|
||||
c.ackFlags++
|
||||
b := make([]byte, 24)
|
||||
binary.LittleEndian.PutUint16(b[0:], magicACK) // 0x0009
|
||||
binary.LittleEndian.PutUint16(b[2:], protoVersion) // 0x000c
|
||||
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // TX seq
|
||||
c.avSeq++
|
||||
binary.LittleEndian.PutUint16(b[8:], c.rxSeqStart) // RX start (last acked)
|
||||
binary.LittleEndian.PutUint16(b[10:], c.rxSeqEnd) // RX end (highest received)
|
||||
if c.rxSeqInit {
|
||||
c.rxSeqStart = c.rxSeqEnd
|
||||
}
|
||||
binary.LittleEndian.PutUint16(b[12:], c.ackFlags) // AckFlags
|
||||
binary.LittleEndian.PutUint32(b[16:], uint32(c.ackFlags)<<16) // AckCounter
|
||||
ts := uint32(time.Now().UnixMilli() & 0xFFFF)
|
||||
binary.LittleEndian.PutUint16(b[20:], uint16(ts)) // Timestamp
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgKeepalive(incoming []byte) []byte {
|
||||
b := make([]byte, 24)
|
||||
copy(b, "\x04\x02\x1a\x0a") // marker + mode
|
||||
binary.LittleEndian.PutUint16(b[4:], 8) // body size
|
||||
binary.LittleEndian.PutUint16(b[8:], cmdKeepaliveReq) // 0x0427
|
||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||
if len(incoming) >= 8 {
|
||||
copy(b[16:], incoming[:8]) // echo payload
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *DTLSConn) msgIOCtrl(payload []byte) []byte {
|
||||
b := make([]byte, 40+len(payload))
|
||||
binary.LittleEndian.PutUint16(b, protoVersion) // magic
|
||||
binary.LittleEndian.PutUint16(b[2:], protoVersion) // version
|
||||
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // av seq
|
||||
c.avSeq++
|
||||
binary.LittleEndian.PutUint16(b[16:], magicIOCtrl) // 0x7000
|
||||
binary.LittleEndian.PutUint16(b[18:], c.seqCmd) // sub channel
|
||||
binary.LittleEndian.PutUint32(b[20:], 1) // ioctl seq
|
||||
binary.LittleEndian.PutUint32(b[24:], uint32(len(payload)+4)) // payload size
|
||||
binary.LittleEndian.PutUint32(b[28:], uint32(c.seqCmd)) // flag
|
||||
b[37] = 0x01
|
||||
copy(b[40:], payload)
|
||||
c.seqCmd++
|
||||
return b
|
||||
}
|
||||
|
||||
func hexDump(data []byte) string {
|
||||
const maxBytes = 650
|
||||
totalLen := len(data)
|
||||
truncated := totalLen > maxBytes
|
||||
if truncated {
|
||||
data = data[:maxBytes]
|
||||
}
|
||||
|
||||
var result string
|
||||
for i := 0; i < len(data); i += 16 {
|
||||
end := min(i+16, len(data))
|
||||
line := fmt.Sprintf(" %04x:", i)
|
||||
for j := i; j < end; j++ {
|
||||
line += fmt.Sprintf(" %02x", data[j])
|
||||
}
|
||||
result += line + "\n"
|
||||
}
|
||||
|
||||
if truncated {
|
||||
result += fmt.Sprintf(" ... (truncated, showing %d of %d bytes)\n", maxBytes, totalLen)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user