Big rewrite tutk proto support
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// I'd like to say hello to Charlie. Your name is forever etched into the history of streaming software.
|
||||
const charlie = "Charlie is the designer of P2P!!"
|
||||
|
||||
func ReverseTransCodePartial(dst, src []byte) []byte {
|
||||
n := len(src)
|
||||
tmp := make([]byte, n)
|
||||
if len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
src16 := src
|
||||
tmp16 := tmp
|
||||
dst16 := dst
|
||||
|
||||
for ; n >= 16; n -= 16 {
|
||||
for i := 0; i != 16; i += 4 {
|
||||
x := binary.LittleEndian.Uint32(src16[i:])
|
||||
binary.LittleEndian.PutUint32(tmp16[i:], bits.RotateLeft32(x, i+3))
|
||||
}
|
||||
|
||||
swap(dst16, tmp16, 16)
|
||||
|
||||
for i := 0; i != 16; i++ {
|
||||
tmp16[i] = dst16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
for i := 0; i != 16; i += 4 {
|
||||
x := binary.LittleEndian.Uint32(tmp16[i:])
|
||||
binary.LittleEndian.PutUint32(dst16[i:], bits.RotateLeft32(x, i+1))
|
||||
}
|
||||
|
||||
tmp16 = tmp16[16:]
|
||||
dst16 = dst16[16:]
|
||||
src16 = src16[16:]
|
||||
}
|
||||
|
||||
swap(tmp16, src16, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
dst16[i] = tmp16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func TransCodePartial(dst, src []byte) []byte {
|
||||
n := len(src)
|
||||
tmp := make([]byte, n)
|
||||
if len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
src16 := src
|
||||
tmp16 := tmp
|
||||
dst16 := dst
|
||||
|
||||
for ; n >= 16; n -= 16 {
|
||||
for i := 0; i != 16; i += 4 {
|
||||
x := binary.LittleEndian.Uint32(src16[i:])
|
||||
binary.LittleEndian.PutUint32(tmp16[i:], bits.RotateLeft32(x, -i-1))
|
||||
}
|
||||
|
||||
for i := 0; i != 16; i++ {
|
||||
dst16[i] = tmp16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
swap(tmp16, dst16, 16)
|
||||
|
||||
for i := 0; i != 16; i += 4 {
|
||||
x := binary.LittleEndian.Uint32(tmp16[i:])
|
||||
binary.LittleEndian.PutUint32(dst16[i:], bits.RotateLeft32(x, -i-3))
|
||||
}
|
||||
|
||||
tmp16 = tmp16[16:]
|
||||
dst16 = dst16[16:]
|
||||
src16 = src16[16:]
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
tmp16[i] = src16[i] ^ charlie[i]
|
||||
}
|
||||
|
||||
swap(dst16, tmp16, n)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func swap(dst, src []byte, n int) {
|
||||
switch n {
|
||||
case 2:
|
||||
_, _ = src[1], dst[1]
|
||||
dst[0] = src[1]
|
||||
dst[1] = src[0]
|
||||
return
|
||||
case 4:
|
||||
_, _ = src[3], dst[3]
|
||||
dst[0] = src[2]
|
||||
dst[1] = src[3]
|
||||
dst[2] = src[0]
|
||||
dst[3] = src[1]
|
||||
return
|
||||
case 8:
|
||||
_, _ = src[7], dst[7]
|
||||
dst[0] = src[7]
|
||||
dst[1] = src[4]
|
||||
dst[2] = src[3]
|
||||
dst[3] = src[2]
|
||||
dst[4] = src[1]
|
||||
dst[5] = src[6]
|
||||
dst[6] = src[5]
|
||||
dst[7] = src[0]
|
||||
return
|
||||
case 16:
|
||||
_, _ = src[15], dst[15]
|
||||
dst[0] = src[11]
|
||||
dst[1] = src[9]
|
||||
dst[2] = src[8]
|
||||
dst[3] = src[15]
|
||||
dst[4] = src[13]
|
||||
dst[5] = src[10]
|
||||
dst[6] = src[12]
|
||||
dst[7] = src[14]
|
||||
dst[8] = src[2]
|
||||
dst[9] = src[1]
|
||||
dst[10] = src[5]
|
||||
dst[11] = src[0]
|
||||
dst[12] = src[6]
|
||||
dst[13] = src[4]
|
||||
dst[14] = src[7]
|
||||
dst[15] = src[3]
|
||||
return
|
||||
}
|
||||
copy(dst, src[:n])
|
||||
}
|
||||
|
||||
const delta = 0x9e3779b9
|
||||
|
||||
func XXTEADecrypt(dst, src, key []byte) {
|
||||
const n = int8(4) // support only 16 bytes src
|
||||
|
||||
var w, k [n]uint32
|
||||
for i := int8(0); i < n; i++ {
|
||||
w[i] = binary.LittleEndian.Uint32(src)
|
||||
k[i] = binary.LittleEndian.Uint32(key)
|
||||
src = src[4:]
|
||||
key = key[4:]
|
||||
}
|
||||
|
||||
rounds := 52/n + 6
|
||||
sum := uint32(rounds) * delta
|
||||
for ; rounds > 0; rounds-- {
|
||||
w0 := w[0]
|
||||
i2 := int8((sum >> 2) & 3)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
wi := w[(i-1)&3]
|
||||
ki := k[i^i2]
|
||||
t1 := (w0 ^ sum) + (wi ^ ki)
|
||||
t2 := (wi >> 5) ^ (w0 << 2)
|
||||
t3 := (w0 >> 3) ^ (wi << 4)
|
||||
w[i] -= t1 ^ (t2 + t3)
|
||||
w0 = w[i]
|
||||
}
|
||||
sum -= delta
|
||||
}
|
||||
|
||||
for _, i := range w {
|
||||
binary.LittleEndian.PutUint32(dst, i)
|
||||
dst = dst[4:]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestXXTEADecrypt(t *testing.T) {
|
||||
buf := []byte("WERhJxb87WF3zgPa")
|
||||
key := []byte("GAgDiwVPg2E4GMke")
|
||||
XXTEADecrypt(buf, buf, key)
|
||||
require.Equal(t, "\xc4\xa6\x2c\xa1\x10\x64\x17\xa5\xda\x02\xe1\x62\xa5\xf0\x62\x71", string(buf))
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package tutk
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// https://github.com/seydx/tutk_wyze#11-codec-reference
|
||||
const (
|
||||
CodecH264 = 0x4e
|
||||
CodecH265 = 0x50
|
||||
CodecPCMA = 0x8a
|
||||
CodecPCML = 0x8c
|
||||
CodecAAC = 0x88
|
||||
)
|
||||
|
||||
func ICAM(cmd uint32, args ...byte) []byte {
|
||||
// 0 4943414d ICAM
|
||||
// 4 d807ff00 command
|
||||
// 8 00000000000000
|
||||
// 15 02 args count
|
||||
// 16 00000000000000
|
||||
// 23 0101 args
|
||||
n := byte(len(args))
|
||||
b := make([]byte, 23+n)
|
||||
copy(b, "ICAM")
|
||||
binary.LittleEndian.PutUint32(b[4:], cmd)
|
||||
b[15] = n
|
||||
copy(b[23:], args)
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Conn) connectDirect(uid string, sid []byte) error {
|
||||
res, err := writeAndWait(
|
||||
c, func(res []byte) bool { return bytes.Index(res, []byte("\x02\x06\x12\x00")) == 8 },
|
||||
ConnectByUID(stageBroadcast, uid, sid),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := len(res) // should be 200
|
||||
c.ver = []byte{res[2], res[n-13], res[n-14], res[n-15], res[n-16]}
|
||||
|
||||
_, err = c.Write(ConnectByUID(stageDirect, uid, sid))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) connectRemote(uid string, sid []byte) error {
|
||||
res, err := writeAndWait(
|
||||
c, func(res []byte) bool { return bytes.Index(res, []byte("\x01\x03\x43")) == 8 },
|
||||
ConnectByUID(stageGetRemoteIP, uid, sid),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read real IP from cloud server response.
|
||||
// Important ot use net.IPv4 because slice will be 16 bytes.
|
||||
c.addr.IP = net.IPv4(res[40], res[41], res[42], res[43])
|
||||
c.addr.Port = int(binary.BigEndian.Uint16(res[38:]))
|
||||
|
||||
res, err = writeAndWait(
|
||||
c, func(res []byte) bool { return bytes.Index(res, []byte("\x04\x04\x33")) == 8 },
|
||||
ConnectByUID(stageRemoteAck, uid, sid),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) == 52 {
|
||||
c.ver = []byte{res[2], res[51], res[50], res[49], res[48]}
|
||||
} else {
|
||||
c.ver = []byte{res[2]}
|
||||
}
|
||||
|
||||
_, err = c.Write(ConnectByUID(stageRemoteOK, uid, sid))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) clientStart(username, password string) error {
|
||||
_, err := writeAndWait(
|
||||
c, func(res []byte) bool {
|
||||
return len(res) >= 84 && res[28] == 0 && (res[29] == 0x14 || res[29] == 0x21)
|
||||
},
|
||||
c.session.ClientStart(0, username, password),
|
||||
c.session.ClientStart(1, username, password),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeAndWait(conn net.Conn, ok func(res []byte) bool, req ...[]byte) ([]byte, error) {
|
||||
var t *time.Timer
|
||||
t = time.AfterFunc(1, func() {
|
||||
for _, b := range req {
|
||||
if _, err := conn.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if t != nil {
|
||||
t.Reset(time.Second)
|
||||
}
|
||||
})
|
||||
defer t.Stop()
|
||||
|
||||
buf := make([]byte, 1200)
|
||||
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok(buf[:n]) {
|
||||
return buf[:n], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
magic = "\x04\x02\x19" // include version 0x19
|
||||
sdkVersion = "\x06\x00\x03\x03" // 3.3.0.6
|
||||
)
|
||||
|
||||
const (
|
||||
stageBroadcast = iota + 1
|
||||
stageDirect
|
||||
stageGetPublicIP
|
||||
stageGetRemoteIP
|
||||
stageRemoteReq
|
||||
stageRemoteAck
|
||||
stageRemoteOK
|
||||
)
|
||||
|
||||
func ConnectByUID(stage byte, uid string, sid8 []byte) []byte {
|
||||
var b []byte
|
||||
|
||||
switch stage {
|
||||
case stageBroadcast, stageDirect:
|
||||
b = make([]byte, 68)
|
||||
copy(b[8:], "\x01\x06\x21")
|
||||
copy(b[52:], sdkVersion)
|
||||
copy(b[56:], sid8)
|
||||
b[64] = stage // 1 or 2
|
||||
|
||||
case stageGetPublicIP:
|
||||
b = make([]byte, 54)
|
||||
copy(b[8:], "\x07\x10\x18")
|
||||
|
||||
case stageGetRemoteIP:
|
||||
b = make([]byte, 112)
|
||||
copy(b[8:], "\x03\x02\x34")
|
||||
copy(b[100:], sid8)
|
||||
b[108] = stageDirect
|
||||
|
||||
case stageRemoteReq:
|
||||
b = make([]byte, 52)
|
||||
copy(b[8:], "\x01\x04\x33")
|
||||
copy(b[36:], sid8)
|
||||
copy(b[48:], sdkVersion)
|
||||
|
||||
case stageRemoteAck:
|
||||
b = make([]byte, 44)
|
||||
copy(b[8:], "\x02\x04\x33")
|
||||
copy(b[36:], sid8)
|
||||
|
||||
case stageRemoteOK:
|
||||
b = make([]byte, 52)
|
||||
copy(b[8:], "\x04\x04\x33")
|
||||
copy(b[36:], sid8)
|
||||
copy(b[48:], sdkVersion)
|
||||
}
|
||||
|
||||
copy(b, magic)
|
||||
b[3] = 0x02 // connection stage
|
||||
binary.LittleEndian.PutUint16(b[4:], uint16(len(b))-16)
|
||||
copy(b[16:], uid)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func GenSessionID() []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(b, uint64(time.Now().UnixNano()))
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Session interface {
|
||||
Close() error
|
||||
|
||||
ClientStart(i byte, username, password string) []byte
|
||||
|
||||
SendIOCtrl(ctrlType uint32, ctrlData []byte) []byte
|
||||
SendFrameData(frameInfo, frameData []byte) []byte
|
||||
|
||||
RecvIOCtrl() (ctrlType uint32, ctrlData []byte, err error)
|
||||
RecvFrameData() (frameInfo, frameData []byte, err error)
|
||||
|
||||
SessionRead(chID byte, buf []byte) int
|
||||
SessionWrite(chID byte, buf []byte) error
|
||||
}
|
||||
|
||||
func NewSession16(conn net.Conn, sid8 []byte) *Session16 {
|
||||
sid16 := make([]byte, 16)
|
||||
copy(sid16[8:], sid8)
|
||||
copy(sid16, sid8[:2])
|
||||
sid16[4] = 0x0c
|
||||
|
||||
return &Session16{
|
||||
conn: conn,
|
||||
sid16: sid16,
|
||||
rawCmd: make(chan []byte, 10),
|
||||
rawPkt: make(chan [2][]byte, 100),
|
||||
}
|
||||
}
|
||||
|
||||
type Session16 struct {
|
||||
conn net.Conn
|
||||
sid16 []byte
|
||||
|
||||
rawCmd chan []byte
|
||||
rawPkt chan [2][]byte
|
||||
|
||||
seqSendCh0 uint16
|
||||
seqSendCh1 uint16
|
||||
|
||||
seqSendCmd1 uint16
|
||||
seqSendAud uint16
|
||||
|
||||
waitSeq uint16
|
||||
waitSize int
|
||||
waitData []byte
|
||||
}
|
||||
|
||||
func (s *Session16) Close() error {
|
||||
close(s.rawCmd)
|
||||
close(s.rawPkt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session16) Msg(size uint16) []byte {
|
||||
b := make([]byte, size)
|
||||
copy(b, magic)
|
||||
b[3] = 0x0a // connected stage
|
||||
binary.LittleEndian.PutUint16(b[4:], size-16)
|
||||
copy(b[8:], "\x07\x04\x21") // client request
|
||||
copy(b[12:], s.sid16)
|
||||
return b
|
||||
}
|
||||
|
||||
const (
|
||||
msgHhrSize = 28
|
||||
cmdHdrSize = 24
|
||||
)
|
||||
|
||||
func (s *Session16) ClientStart(i byte, username, password string) []byte {
|
||||
const size = 566 + 32
|
||||
msg := s.Msg(size)
|
||||
|
||||
// 0 00000b0000000000000000000000000022020000fcfc7284
|
||||
// 24 4d69737300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
// 281 636c69656e740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
// 538 0100000004000000fb071f000000000000000000000003000000000001000000
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x00\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[16:], size-52)
|
||||
if i == 0 {
|
||||
cmd[18] = 1
|
||||
} else {
|
||||
cmd[1] = 0x20
|
||||
}
|
||||
binary.LittleEndian.PutUint32(cmd[20:], uint32(time.Now().UnixMilli()))
|
||||
|
||||
// important values for some cameras (not for df3)
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, username)
|
||||
copy(data[257:], password)
|
||||
|
||||
// 0100000004000000fb071f000000000000000000000003000000000001000000
|
||||
cfg := data[257+257:]
|
||||
//cfg[0] = 1 // 0 - simple proto, 1 - complex proto with "0Cxx" commands
|
||||
cfg[4] = 4
|
||||
copy(cfg[8:], "\xfb\x07\x1f\x00")
|
||||
cfg[22] = 3
|
||||
//cfg[28] = 1 // unknown
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) SendIOCtrl(ctrlType uint32, ctrlData []byte) []byte {
|
||||
dataSize := 4 + uint16(len(ctrlData))
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize + dataSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x70\x0b\x00")
|
||||
|
||||
s.seqSendCmd1++ // start from 1, important!
|
||||
binary.LittleEndian.PutUint16(cmd[4:], s.seqSendCmd1)
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[16:], dataSize)
|
||||
binary.LittleEndian.PutUint32(cmd[20:], uint32(time.Now().UnixMilli()))
|
||||
|
||||
data := cmd[cmdHdrSize:]
|
||||
binary.LittleEndian.PutUint32(data, ctrlType)
|
||||
copy(data[4:], ctrlData)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) SendFrameData(frameInfo, frameData []byte) []byte {
|
||||
// -> 01030b001d0000008802000000002800b0020bf501000000 ... 4f4455412000000088020000030400001d000000000000000bf51f7a9b0100000000000000000000
|
||||
|
||||
n := uint16(len(frameData))
|
||||
dataSize := n + 8 + 32
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize + dataSize)
|
||||
|
||||
// 0 01030b00 command + version
|
||||
// 4 1d000000 seq
|
||||
// 8 8802 media size (648)
|
||||
// 10 00000000
|
||||
// 14 2800 tail (pkt header) size?
|
||||
// 16 b002 size (648 + 8 + 32)
|
||||
// 18 0bf5 random msg id (unixms)
|
||||
// 20 01000000 fixed
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x01\x03\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[4:], s.seqSendAud)
|
||||
s.seqSendAud++
|
||||
binary.LittleEndian.PutUint16(cmd[8:], n)
|
||||
cmd[14] = 0x28 // important!
|
||||
binary.LittleEndian.PutUint16(cmd[16:], dataSize)
|
||||
binary.LittleEndian.PutUint16(cmd[18:], uint16(time.Now().UnixMilli()))
|
||||
cmd[20] = 1
|
||||
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, frameData)
|
||||
copy(data[n:], "ODUA\x20\x00\x00\x00")
|
||||
copy(data[n+8:], frameInfo)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) RecvIOCtrl() (ctrlType uint32, ctrlData []byte, err error) {
|
||||
buf, ok := <-s.rawCmd
|
||||
if !ok {
|
||||
return 0, nil, io.EOF
|
||||
}
|
||||
return binary.LittleEndian.Uint32(buf), buf[4:], nil
|
||||
}
|
||||
|
||||
func (s *Session16) RecvFrameData() (frameInfo, frameData []byte, err error) {
|
||||
buf, ok := <-s.rawPkt
|
||||
if !ok {
|
||||
return nil, nil, io.EOF
|
||||
}
|
||||
return buf[0], buf[1], nil
|
||||
}
|
||||
|
||||
func (s *Session16) SessionRead(chID byte, cmd []byte) int {
|
||||
if chID != 0 {
|
||||
return s.handleCh1(cmd)
|
||||
}
|
||||
|
||||
// 0 01030800 command + version
|
||||
// 4 00000000 frame num
|
||||
// 8 ac880100 total size
|
||||
// 12 6200 chunk seq
|
||||
// 14 2000 tail (pkt header) size
|
||||
// 16 cc00 size
|
||||
// 18 0000
|
||||
// 20 01000000 fixed
|
||||
|
||||
switch cmd[0] {
|
||||
case 0x01:
|
||||
var packetData [2][]byte
|
||||
|
||||
switch cmd[1] {
|
||||
case 0x03:
|
||||
seq := binary.LittleEndian.Uint16(cmd[12:])
|
||||
if seq != s.waitSeq {
|
||||
s.waitSeq = 0
|
||||
return msgMediaLost
|
||||
}
|
||||
if seq == 0 {
|
||||
s.waitData = s.waitData[:0]
|
||||
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
||||
hdrSize := binary.LittleEndian.Uint16(cmd[14:])
|
||||
s.waitSize = int(hdrSize) + int(payloadSize)
|
||||
}
|
||||
|
||||
s.waitData = append(s.waitData, cmd[24:]...)
|
||||
if n := len(s.waitData); n < s.waitSize {
|
||||
s.waitSeq++
|
||||
return msgMediaChunk
|
||||
}
|
||||
|
||||
s.waitSeq = 0
|
||||
|
||||
payloadSize := binary.LittleEndian.Uint32(cmd[8:])
|
||||
packetData[0] = bytes.Clone(s.waitData[payloadSize:])
|
||||
packetData[1] = bytes.Clone(s.waitData[:payloadSize])
|
||||
|
||||
case 0x04:
|
||||
data := cmd[24:]
|
||||
hdrSize := binary.LittleEndian.Uint16(cmd[14:])
|
||||
packetData[0] = bytes.Clone(data[:hdrSize])
|
||||
packetData[1] = bytes.Clone(data[hdrSize:])
|
||||
|
||||
default:
|
||||
return msgUnknown
|
||||
}
|
||||
|
||||
select {
|
||||
case s.rawPkt <- packetData:
|
||||
default:
|
||||
return msgError
|
||||
}
|
||||
return msgMediaFrame
|
||||
|
||||
case 0x00:
|
||||
switch cmd[1] {
|
||||
case 0x70:
|
||||
_ = s.SessionWrite(0, s.msgAck0070(cmd))
|
||||
select {
|
||||
case s.rawCmd <- append([]byte{}, cmd[24:]...):
|
||||
default:
|
||||
}
|
||||
|
||||
return msgCommand
|
||||
case 0x12:
|
||||
_ = s.SessionWrite(0, s.msgAck0012(cmd))
|
||||
return msgDafang0012
|
||||
case 0x71:
|
||||
return msgCommandAck
|
||||
}
|
||||
}
|
||||
|
||||
return msgUnknown
|
||||
}
|
||||
|
||||
func (s *Session16) msgAck0070(msg28 []byte) []byte {
|
||||
// <- 00700800010000000000000000000000340000007625a02f ...
|
||||
// -> 00710800010000000000000000000000000000007625a02f
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x71")
|
||||
copy(cmd[2:], msg28[2:6]) // same version and seq
|
||||
copy(cmd[20:], msg28[20:24]) // same msg random
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) msgAck0012(msg28 []byte) []byte {
|
||||
// <- 001208000000000000000000000000000c00000000000000 020000000100000001000000
|
||||
// -> 00130b000000000000000000000000001400000000000000 0200000001000000010000000000000000000000
|
||||
const dataSize = 20
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize + dataSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x13\x0b\x00")
|
||||
cmd[16] = dataSize
|
||||
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, msg28[cmdHdrSize:])
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) handleCh1(cmd []byte) int {
|
||||
// Channel 1 used for two-way audio. It's important:
|
||||
// - answer on 0000 command with exact config response (can't set simple proto)
|
||||
// - send 0012 command at start
|
||||
// - respond on every 0008 command for smooth playback
|
||||
switch cid := string(cmd[:2]); cid {
|
||||
case "\x00\x00": // client start
|
||||
_ = s.SessionWrite(1, s.msgAck0000(cmd))
|
||||
_ = s.SessionWrite(1, s.msg0012())
|
||||
return msgClientStart
|
||||
case "\x00\x07": // time sync without data
|
||||
_ = s.SessionWrite(1, s.msgAck0007(cmd))
|
||||
return msgUnknown0007
|
||||
case "\x00\x08": // time sync with data
|
||||
_ = s.SessionWrite(1, s.msgAck0008(cmd))
|
||||
return msgUnknown0008
|
||||
case "\x00\x13": // ack for 0012
|
||||
return msgUnknown0013
|
||||
}
|
||||
return msgUnknown
|
||||
}
|
||||
|
||||
func (s *Session16) msgAck0000(msg28 []byte) []byte {
|
||||
// <- 000008000000000000000000000000001a0200004f47c714 ... 00000000000000000100000004000000fb071f00000000000000000000000300
|
||||
// -> 00140b00000000000000000000000000200000004f47c714 00000000000000000100000004000000fb071f00000000000000000000000300
|
||||
const cmdDataSize = 32
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize + cmdDataSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x14\x0b\x00")
|
||||
cmd[16] = cmdDataSize
|
||||
copy(cmd[20:], msg28[20:24]) // request id (random)
|
||||
|
||||
// Important to answer with same data.
|
||||
data := cmd[cmdHdrSize:]
|
||||
copy(data, msg28[len(msg28)-32:])
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) msg0012() []byte {
|
||||
// -> 00120b000000000000000000000000000c00000000000000020000000100000001000000
|
||||
const dataSize = 12
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize + dataSize)
|
||||
cmd := msg[msgHhrSize:]
|
||||
|
||||
copy(cmd, "\x00\x12\x0b\x00")
|
||||
cmd[16] = dataSize
|
||||
data := cmd[cmdHdrSize:]
|
||||
|
||||
data[0] = 2
|
||||
data[4] = 1
|
||||
data[9] = 1
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) msgAck0007(msg28 []byte) []byte {
|
||||
// <- 000708000000000000000000000000000c00000001000000000000001c551f7a00000000
|
||||
// -> 010a0b00000000000000000000000000000000000100000000000000
|
||||
msg := s.Msg(msgHhrSize + 28)
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x01\x0a\x0b\x00")
|
||||
cmd[20] = 1
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) msgAck0008(msg28 []byte) []byte {
|
||||
// <- 000808000000000000000000000000000000f9f0010000000200000050f31f7a
|
||||
// -> 01090b0000000000000000000000000000000000010000000200000050f31f7a
|
||||
msg := s.Msg(msgHhrSize + 28)
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x01\x09\x0b\x00")
|
||||
copy(cmd[20:], msg28[20:])
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session16) SessionWrite(chID byte, buf []byte) error {
|
||||
switch chID {
|
||||
case 0:
|
||||
binary.LittleEndian.PutUint16(buf[6:], s.seqSendCh0)
|
||||
s.seqSendCh0++
|
||||
case 1:
|
||||
binary.LittleEndian.PutUint16(buf[6:], s.seqSendCh1)
|
||||
s.seqSendCh1++
|
||||
buf[14] = 1 // channel
|
||||
}
|
||||
_, err := s.conn.Write(buf)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package tutk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewSession25(conn net.Conn, sid []byte) *Session25 {
|
||||
return &Session25{
|
||||
Session16: NewSession16(conn, sid),
|
||||
rb: NewReorderBuffer(5),
|
||||
}
|
||||
}
|
||||
|
||||
type Session25 struct {
|
||||
*Session16
|
||||
|
||||
rb *ReorderBuffer
|
||||
|
||||
seqSendCmd2 uint16
|
||||
seqSendCnt uint16
|
||||
|
||||
seqRecvPkt0 uint16
|
||||
seqRecvPkt1 uint16
|
||||
seqRecvCmd2 uint16
|
||||
}
|
||||
|
||||
const cmdHdrSize25 = 28
|
||||
|
||||
func (s *Session25) SendIOCtrl(ctrlType uint32, ctrlData []byte) []byte {
|
||||
size := msgHhrSize + cmdHdrSize25 + 4 + uint16(len(ctrlData))
|
||||
msg := s.Msg(size)
|
||||
|
||||
// 0 0070 command
|
||||
// 2 0b00 version
|
||||
// 4 1000 seq
|
||||
// 6 0076 ???
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x70\x0b\x00")
|
||||
binary.LittleEndian.PutUint16(cmd[4:], s.seqSendCmd1)
|
||||
s.seqSendCmd1++
|
||||
|
||||
// 8 0070 command (second time)
|
||||
// 10 0300 seq
|
||||
// 12 0100 chunks count
|
||||
// 14 0000 chunk seq (starts from 0)
|
||||
// 16 5500 size
|
||||
// 18 0000 random msg id (always 0)
|
||||
// 20 03000000 seq (second time)
|
||||
// 24 00000000
|
||||
// 28 01010000 ctrlType
|
||||
cmd[9] = 0x70
|
||||
cmd[12] = 1
|
||||
binary.LittleEndian.PutUint16(cmd[16:], size-52)
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[10:], s.seqSendCmd2)
|
||||
binary.LittleEndian.PutUint16(cmd[20:], s.seqSendCmd2)
|
||||
s.seqSendCmd2++
|
||||
|
||||
data := cmd[28:]
|
||||
binary.LittleEndian.PutUint32(data, ctrlType)
|
||||
copy(data[4:], ctrlData)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session25) SendFrameData(frameInfo, frameData []byte) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session25) SessionRead(chID byte, cmd []byte) (res int) {
|
||||
if chID != 0 {
|
||||
return s.handleCh1(cmd)
|
||||
}
|
||||
|
||||
switch cmd[0] {
|
||||
case 0x03, 0x05, 0x07:
|
||||
for i := 0; cmd != nil; i++ {
|
||||
res = s.handleChunk(cmd, i == 0)
|
||||
cmd = s.rb.Pop()
|
||||
}
|
||||
return
|
||||
|
||||
case 0x00:
|
||||
_ = s.SessionWrite(0, s.msgAckCounters())
|
||||
s.seqRecvCmd2 = binary.LittleEndian.Uint16(cmd[2:])
|
||||
|
||||
switch cmd[1] {
|
||||
case 0x10:
|
||||
return msgUnknown0010 // unknown
|
||||
case 0x21:
|
||||
return msgClientStartAck2
|
||||
case 0x70:
|
||||
select {
|
||||
case s.rawCmd <- cmd[28:]:
|
||||
default:
|
||||
}
|
||||
return msgCommand // cmd from camera
|
||||
case 0x71:
|
||||
return msgCommandAck
|
||||
}
|
||||
|
||||
case 0x09:
|
||||
// off sample
|
||||
// 0 09000b00 cmd1
|
||||
// 4 0d000000 seqCmd1
|
||||
// 12 0000 seqRecvCmd2
|
||||
seq := binary.LittleEndian.Uint16(cmd[12:])
|
||||
if s.seqSendCmd1 > seq {
|
||||
return msgCommandAck
|
||||
}
|
||||
return msgCounters
|
||||
|
||||
case 0x0a:
|
||||
// seq sample
|
||||
// 0 0a080b00
|
||||
// 4 03000000
|
||||
// 8 e2043200
|
||||
// 12 01000000
|
||||
_ = s.SessionWrite(0, s.msgAck0A08(cmd))
|
||||
return msgUnknown0a08
|
||||
}
|
||||
|
||||
return msgUnknown
|
||||
}
|
||||
|
||||
func (s *Session25) handleChunk(cmd []byte, checkSeq bool) int {
|
||||
var cmd2 []byte
|
||||
|
||||
flags := cmd[1]
|
||||
if flags&0b1000 == 0 {
|
||||
// off sample
|
||||
// 0 0700 command
|
||||
// 2 0b00 version
|
||||
// 4 2700 seq
|
||||
// 6 0000 ???
|
||||
// 8 0700 command (second time)
|
||||
// 10 1400 seq
|
||||
// 12 1300 chunks count per this frame
|
||||
// 14 1100 chunk seq, starts from 0 (0x20 for last chunk)
|
||||
// 16 0004 frame data size
|
||||
// 18 0000 random msg id (always 0)
|
||||
// 20 02000000 previous frame seq, starts from 0
|
||||
// 24 03000000 current frame seq, starts from 1
|
||||
cmd2 = cmd[8:]
|
||||
} else {
|
||||
// off sample
|
||||
// 0 070d0b00
|
||||
// 4 30000000
|
||||
// 8 5c965500 ???
|
||||
// 12 ffff0000 ???
|
||||
// 16 0701 fixed command
|
||||
// 18 190001002000a802000006000000070000000
|
||||
cmd2 = cmd[16:]
|
||||
}
|
||||
|
||||
seq := binary.LittleEndian.Uint16(cmd2[2:])
|
||||
|
||||
if checkSeq {
|
||||
if s.rb.Check(seq) {
|
||||
s.rb.Next()
|
||||
} else {
|
||||
s.rb.Push(seq, cmd)
|
||||
return msgMediaReorder
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is first chunk for frame.
|
||||
// Handle protocol bug "0x20 chunk seq for last chunk" and sometimes
|
||||
// "0x20 chunk seq for first chunk if only one chunk".
|
||||
if binary.LittleEndian.Uint16(cmd2[6:]) == 0 || binary.LittleEndian.Uint16(cmd2[4:]) == 1 {
|
||||
s.waitData = s.waitData[:0]
|
||||
s.waitSeq = seq
|
||||
} else if seq != s.waitSeq {
|
||||
return msgMediaLost
|
||||
}
|
||||
|
||||
s.waitData = append(s.waitData, cmd2[20:]...)
|
||||
|
||||
if flags&0b0001 == 0 {
|
||||
s.waitSeq++
|
||||
return msgMediaChunk
|
||||
}
|
||||
|
||||
s.seqRecvPkt1 = seq
|
||||
_ = s.SessionWrite(0, s.msgAckCounters())
|
||||
|
||||
n := len(s.waitData) - 32
|
||||
packetData := [2][]byte{bytes.Clone(s.waitData[n:]), bytes.Clone(s.waitData[:n])}
|
||||
|
||||
select {
|
||||
case s.rawPkt <- packetData:
|
||||
default:
|
||||
return msgError
|
||||
}
|
||||
return msgMediaFrame
|
||||
}
|
||||
|
||||
func (s *Session25) msgAckCounters() []byte {
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize)
|
||||
|
||||
// off sample
|
||||
// 0 09000b00 cmd1
|
||||
// 4 2700 seqCmd1
|
||||
// 6 0000
|
||||
// 8 1300 seqRecvPkt0
|
||||
// 10 2600 seqRecvPkt1
|
||||
// 12 0400 seqRecvCmd2
|
||||
// 14 00000000
|
||||
// 18 1400 seqSendCnt
|
||||
// 20 d91a random
|
||||
// 22 0000
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x09\x00\x0b\x00")
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[4:], s.seqSendCmd1)
|
||||
s.seqSendCmd1++
|
||||
|
||||
// seqRecvPkt0 stores previous value of seqRecvPkt1
|
||||
// don't understand why this needs
|
||||
binary.LittleEndian.PutUint16(cmd[8:], s.seqRecvPkt0)
|
||||
s.seqRecvPkt0 = s.seqRecvPkt1
|
||||
binary.LittleEndian.PutUint16(cmd[10:], s.seqRecvPkt1)
|
||||
binary.LittleEndian.PutUint16(cmd[12:], s.seqRecvCmd2)
|
||||
|
||||
binary.LittleEndian.PutUint16(cmd[18:], s.seqSendCnt)
|
||||
s.seqSendCnt++
|
||||
binary.LittleEndian.PutUint16(cmd[20:], uint16(time.Now().UnixMilli()))
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session25) handleCh1(cmd []byte) int {
|
||||
// Channel 1 used for two-way audio. It's important:
|
||||
// - answer on 0000 command with exact config response (can't set simple proto)
|
||||
// - send 0012 command at start
|
||||
// - respond on every 0008 command for smooth playback
|
||||
switch cid := string(cmd[:2]); cid {
|
||||
case "\x00\x00": // client start
|
||||
return msgClientStart
|
||||
case "\x00\x07": // time sync without data
|
||||
_ = s.SessionWrite(1, s.msgAck0007(cmd))
|
||||
return msgUnknown0007
|
||||
case "\x00\x20": // client start2
|
||||
_ = s.SessionWrite(1, s.msgAck0020(cmd))
|
||||
return msgClientStart2
|
||||
case "\x09\x00":
|
||||
return msgUnknown0900
|
||||
case "\x0a\x08":
|
||||
return msgUnknown0a08
|
||||
}
|
||||
return msgUnknown
|
||||
}
|
||||
|
||||
func (s *Session25) msgAck0020(msg28 []byte) []byte {
|
||||
const cmdDataSize = 36
|
||||
|
||||
msg := s.Msg(msgHhrSize + cmdHdrSize25 + cmdDataSize)
|
||||
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x00\x21\x0b\x00")
|
||||
cmd[16] = cmdDataSize
|
||||
copy(cmd[20:], msg28[20:24]) // request id (random)
|
||||
|
||||
// 0 00000000
|
||||
// 4 00010001
|
||||
// 8 01000000
|
||||
// 12 04000000
|
||||
// 16 fb071f00
|
||||
// 20 00000000
|
||||
// 24 00000000
|
||||
// 28 00000300
|
||||
// 32 01000000
|
||||
data := cmd[cmdHdrSize25:]
|
||||
data[5] = 1
|
||||
data[7] = 1
|
||||
data[8] = 1
|
||||
data[12] = 4
|
||||
copy(data[16:], "\xfb\x07\x1f\x00")
|
||||
data[30] = 3
|
||||
data[32] = 1
|
||||
return msg
|
||||
}
|
||||
|
||||
func (s *Session25) msgAck0A08(msg28 []byte) []byte {
|
||||
// <- 0a080b005b0000000b51590002000000
|
||||
// -> 0b000b00000001000b5103000300000000000000
|
||||
msg := s.Msg(msgHhrSize + 20)
|
||||
cmd := msg[msgHhrSize:]
|
||||
copy(cmd, "\x0b\x00\x0b\x00")
|
||||
copy(cmd[8:], msg28[8:10])
|
||||
return msg
|
||||
}
|
||||
|
||||
// ReorderBuffer used for UDP incoming data. Because the order of the packets may be mixed up.
|
||||
type ReorderBuffer struct {
|
||||
buf map[uint16][]byte
|
||||
seq uint16
|
||||
size int
|
||||
}
|
||||
|
||||
func NewReorderBuffer(size int) *ReorderBuffer {
|
||||
return &ReorderBuffer{buf: make(map[uint16][]byte), size: size}
|
||||
}
|
||||
|
||||
// Check return OK if this is the seq we are waiting for.
|
||||
func (r *ReorderBuffer) Check(seq uint16) (ok bool) {
|
||||
return seq == r.seq
|
||||
}
|
||||
|
||||
func (r *ReorderBuffer) Next() {
|
||||
r.seq++
|
||||
}
|
||||
|
||||
// Available return how much free slots is in the buffer.
|
||||
func (r *ReorderBuffer) Available() int {
|
||||
return r.size - len(r.buf)
|
||||
}
|
||||
|
||||
// Push new item to buffer. Important! There is no buffer full check here.
|
||||
func (r *ReorderBuffer) Push(seq uint16, data []byte) {
|
||||
//log.Printf("push seq=%d wait=%d", seq, r.seq)
|
||||
r.buf[seq] = bytes.Clone(data)
|
||||
}
|
||||
|
||||
// Pop latest item from buffer. OK - if items wasn't dropped.
|
||||
func (r *ReorderBuffer) Pop() []byte {
|
||||
for {
|
||||
if data := r.buf[r.seq]; data != nil {
|
||||
delete(r.buf, r.seq)
|
||||
r.Next()
|
||||
//log.Printf("pop seq=%d", r.seq)
|
||||
return data
|
||||
}
|
||||
if r.Available() > 0 {
|
||||
return nil
|
||||
}
|
||||
//log.Printf("drop seq=%d", r.seq)
|
||||
r.Next() // drop item
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user