refactor
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
package tutk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CalculateAuthKey(enr, mac string) []byte {
|
||||||
|
data := enr + strings.ToUpper(mac)
|
||||||
|
hash := sha256.Sum256([]byte(data))
|
||||||
|
b64 := base64.StdEncoding.EncodeToString(hash[:6])
|
||||||
|
b64 = strings.ReplaceAll(b64, "+", "Z")
|
||||||
|
b64 = strings.ReplaceAll(b64, "/", "9")
|
||||||
|
b64 = strings.ReplaceAll(b64, "=", "A")
|
||||||
|
return []byte(b64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DerivePSK(enr string) []byte {
|
||||||
|
// DerivePSK derives the DTLS PSK from ENR
|
||||||
|
// TUTK SDK treats the PSK as a NULL-terminated C string, so if SHA256(ENR)
|
||||||
|
// contains a 0x00 byte, the PSK is truncated at that position.
|
||||||
|
hash := sha256.Sum256([]byte(enr))
|
||||||
|
pskLen := 32
|
||||||
|
for i := range 32 {
|
||||||
|
if hash[i] == 0x00 {
|
||||||
|
pskLen = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
psk := make([]byte, 32)
|
||||||
|
copy(psk[:pskLen], hash[:pskLen])
|
||||||
|
return psk
|
||||||
|
}
|
||||||
@@ -72,7 +72,7 @@ func computeNonce(iv []byte, epoch uint16, sequenceNumber uint64) []byte {
|
|||||||
binary.BigEndian.PutUint64(nonce[4:], sequenceNumber)
|
binary.BigEndian.PutUint64(nonce[4:], sequenceNumber)
|
||||||
binary.BigEndian.PutUint16(nonce[4:], epoch)
|
binary.BigEndian.PutUint16(nonce[4:], epoch)
|
||||||
|
|
||||||
for i := 0; i < chachaNonceLength; i++ {
|
for i := range chachaNonceLength {
|
||||||
nonce[i] ^= iv[i]
|
nonce[i] ^= iv[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package tutk
|
||||||
|
|
||||||
|
// https://github.com/seydx/tutk_wyze#11-codec-reference
|
||||||
|
const (
|
||||||
|
CodecMPEG4 byte = 0x4C
|
||||||
|
CodecH263 byte = 0x4D
|
||||||
|
CodecH264 byte = 0x4E
|
||||||
|
CodecMJPEG byte = 0x4F
|
||||||
|
CodecH265 byte = 0x50
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CodecAACRaw byte = 0x86
|
||||||
|
CodecAACADTS byte = 0x87
|
||||||
|
CodecAACLATM byte = 0x88
|
||||||
|
CodecPCMU byte = 0x89
|
||||||
|
CodecPCMA byte = 0x8A
|
||||||
|
CodecADPCM byte = 0x8B
|
||||||
|
CodecPCML byte = 0x8C
|
||||||
|
CodecSPEEX byte = 0x8D
|
||||||
|
CodecMP3 byte = 0x8E
|
||||||
|
CodecG726 byte = 0x8F
|
||||||
|
CodecAACAlt byte = 0x90
|
||||||
|
CodecOpus byte = 0x92
|
||||||
|
)
|
||||||
|
|
||||||
|
var sampleRates = [9]uint32{8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}
|
||||||
|
|
||||||
|
func GetSamplesPerFrame(codecID byte) uint32 {
|
||||||
|
switch codecID {
|
||||||
|
case CodecAACRaw, CodecAACADTS, CodecAACLATM, CodecAACAlt:
|
||||||
|
return 1024
|
||||||
|
case CodecPCMU, CodecPCMA, CodecPCML, CodecADPCM, CodecSPEEX, CodecG726:
|
||||||
|
return 160
|
||||||
|
case CodecMP3:
|
||||||
|
return 1152
|
||||||
|
case CodecOpus:
|
||||||
|
return 960
|
||||||
|
default:
|
||||||
|
return 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsVideoCodec(id byte) bool {
|
||||||
|
return id >= CodecMPEG4 && id <= CodecH265
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAudioCodec(id byte) bool {
|
||||||
|
return id >= CodecAACRaw && id <= CodecOpus
|
||||||
|
}
|
||||||
@@ -3,31 +3,71 @@ package tutk
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/wyze/crypto"
|
|
||||||
"github.com/pion/dtls/v3"
|
"github.com/pion/dtls/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaxPacketSize = 2048
|
magicCC51 = "\x51\xcc" // (wyze specific?)
|
||||||
ReadBufferSize = 2 * 1024 * 1024
|
sdkVersion42 = "\x01\x01\x02\x04" // 4.2.1.1
|
||||||
DiscoTimeout = 5000 * time.Millisecond
|
sdkVersion43 = "\x00\x08\x03\x04" // 4.3.8.0
|
||||||
DiscoInterval = 100 * time.Millisecond
|
|
||||||
SessionTimeout = 5000 * time.Millisecond
|
|
||||||
ReadWaitInterval = 50 * time.Millisecond
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
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
|
conn *net.UDPConn
|
||||||
addr *net.UDPAddr
|
addr *net.UDPAddr
|
||||||
frames *FrameHandler
|
frames *FrameHandler
|
||||||
@@ -49,17 +89,15 @@ type Conn struct {
|
|||||||
uid string
|
uid string
|
||||||
authKey string
|
authKey string
|
||||||
enr string
|
enr string
|
||||||
mac string
|
|
||||||
psk []byte
|
psk []byte
|
||||||
rid []byte
|
|
||||||
|
|
||||||
// Session
|
// Session
|
||||||
sid []byte
|
sid []byte
|
||||||
ticket uint16
|
ticket uint16
|
||||||
avResp *AVLoginResponse
|
hasTwoWayStreaming bool
|
||||||
|
|
||||||
// Protocol
|
// Protocol
|
||||||
newProto bool
|
isCC51 bool
|
||||||
seq uint16
|
seq uint16
|
||||||
seqCmd uint16
|
seqCmd uint16
|
||||||
avSeq uint32
|
avSeq uint32
|
||||||
@@ -75,34 +113,32 @@ type Conn struct {
|
|||||||
cmdAck func()
|
cmdAck func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Dial(host string, port int, uid, authKey, enr, mac string, verbose bool) (*Conn, error) {
|
func DialDTLS(host string, port int, uid, authKey, enr string, verbose bool) (*DTLSConn, error) {
|
||||||
udp, err := net.ListenUDP("udp", nil)
|
udp, err := net.ListenUDP("udp", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = udp.SetReadBuffer(ReadBufferSize)
|
_ = udp.SetReadBuffer(2 * 1024 * 1024)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
psk := derivePSK(enr)
|
psk := DerivePSK(enr)
|
||||||
|
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = DefaultPort
|
port = 32761
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Conn{
|
c := &DTLSConn{
|
||||||
conn: udp,
|
conn: udp,
|
||||||
addr: &net.UDPAddr{IP: net.ParseIP(host), Port: port},
|
addr: &net.UDPAddr{IP: net.ParseIP(host), Port: port},
|
||||||
rid: genRandomID(),
|
|
||||||
uid: uid,
|
uid: uid,
|
||||||
authKey: authKey,
|
authKey: authKey,
|
||||||
enr: enr,
|
enr: enr,
|
||||||
mac: mac,
|
|
||||||
psk: psk,
|
psk: psk,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
rxSeqStart: 0xffff, // Initialize RX seq for ACK
|
rxSeqStart: 0xffff,
|
||||||
rxSeqEnd: 0xffff,
|
rxSeqEnd: 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,10 +166,10 @@ func Dial(host string, port int, uid, authKey, enr, mac string, verbose bool) (*
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) AVClientStart(timeout time.Duration) error {
|
func (c *DTLSConn) AVClientStart(timeout time.Duration) error {
|
||||||
randomID := genRandomID()
|
randomID := GenSessionID()
|
||||||
pkt1 := c.buildAVLoginPacket(MagicAVLogin1, 570, 0x0001, randomID)
|
pkt1 := c.msgAVLogin(magicAVLogin1, 570, 0x0001, randomID)
|
||||||
pkt2 := c.buildAVLoginPacket(MagicAVLogin2, 572, 0x0000, randomID)
|
pkt2 := c.msgAVLogin(magicAVLogin2, 572, 0x0000, randomID)
|
||||||
pkt2[20]++ // pkt2 has randomID incremented by 1
|
pkt2[20]++ // pkt2 has randomID incremented by 1
|
||||||
|
|
||||||
if _, err := c.clientConn.Write(pkt1); err != nil {
|
if _, err := c.clientConn.Write(pkt1); err != nil {
|
||||||
@@ -155,16 +191,13 @@ func (c *Conn) AVClientStart(timeout time.Duration) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
if len(data) >= 32 && binary.LittleEndian.Uint16(data) == MagicAVLoginResp {
|
if len(data) >= 32 && binary.LittleEndian.Uint16(data) == magicAVLoginResp {
|
||||||
c.avResp = &AVLoginResponse{
|
c.hasTwoWayStreaming = data[31] == 1
|
||||||
ServerType: binary.LittleEndian.Uint32(data[4:]),
|
|
||||||
Resend: int32(data[29]),
|
|
||||||
TwoWayStreaming: int32(data[31]),
|
|
||||||
}
|
|
||||||
|
|
||||||
ack := c.buildACK()
|
ack := c.msgACK()
|
||||||
c.clientConn.Write(ack)
|
c.clientConn.Write(ack)
|
||||||
|
|
||||||
|
// Start ACK sender for continuous streaming
|
||||||
c.wg.Add(1)
|
c.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer c.wg.Done()
|
defer c.wg.Done()
|
||||||
@@ -177,7 +210,7 @@ func (c *Conn) AVClientStart(timeout time.Duration) error {
|
|||||||
return
|
return
|
||||||
case <-ackTicker.C:
|
case <-ackTicker.C:
|
||||||
if c.clientConn != nil {
|
if c.clientConn != nil {
|
||||||
ack := c.buildACK()
|
ack := c.msgACK()
|
||||||
c.clientConn.Write(ack)
|
c.clientConn.Write(ack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,14 +225,9 @@ func (c *Conn) AVClientStart(timeout time.Duration) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) AVServStart() error {
|
func (c *DTLSConn) AVServStart() error {
|
||||||
if c.verbose {
|
adapter := NewChannelAdapter(c.ctx, iotcChannelBack, c.addr, c.WriteDTLS, c.serverBuf)
|
||||||
fmt.Printf("[DTLS] Waiting for client handshake on channel %d\n", IOTCChannelBack)
|
conn, err := NewDTLSServer(adapter, c.addr, c.psk)
|
||||||
fmt.Printf("[DTLS] PSK Identity: %s\n", PSKIdentity)
|
|
||||||
fmt.Printf("[DTLS] PSK Key: %s\n", hex.EncodeToString(c.psk))
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := NewDtlsServer(c, IOTCChannelBack, c.psk)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dtls: server handshake failed: %w", err)
|
return fmt.Errorf("dtls: server handshake failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -209,7 +237,7 @@ func (c *Conn) AVServStart() error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[DTLS] Server handshake complete on channel %d\n", IOTCChannelBack)
|
fmt.Printf("[DTLS] Server handshake complete on channel %d\n", iotcChannelBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for and respond to AV Login request from camera
|
// Wait for and respond to AV Login request from camera
|
||||||
@@ -220,10 +248,11 @@ func (c *Conn) AVServStart() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) AVServStop() error {
|
func (c *DTLSConn) AVServStop() error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
serverConn := c.serverConn
|
serverConn := c.serverConn
|
||||||
c.serverConn = nil
|
c.serverConn = nil
|
||||||
|
|
||||||
// Reset audio TX state
|
// Reset audio TX state
|
||||||
c.audioSeq = 0
|
c.audioSeq = 0
|
||||||
c.audioFrameNo = 0
|
c.audioFrameNo = 0
|
||||||
@@ -238,7 +267,7 @@ func (c *Conn) AVServStop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) AVRecvFrameData() (*Packet, error) {
|
func (c *DTLSConn) AVRecvFrameData() (*Packet, error) {
|
||||||
select {
|
select {
|
||||||
case pkt, ok := <-c.frames.Recv():
|
case pkt, ok := <-c.frames.Recv():
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -250,7 +279,7 @@ func (c *Conn) AVRecvFrameData() (*Packet, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) AVSendAudioData(codec uint16, payload []byte, timestampUS uint32, sampleRate uint32, channels uint8) error {
|
func (c *DTLSConn) AVSendAudioData(codec byte, payload []byte, timestampUS uint32, sampleRate uint32, channels uint8) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
conn := c.serverConn
|
conn := c.serverConn
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
@@ -258,7 +287,7 @@ func (c *Conn) AVSendAudioData(codec uint16, payload []byte, timestampUS uint32,
|
|||||||
return fmt.Errorf("speaker channel not connected")
|
return fmt.Errorf("speaker channel not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
frame := c.buildAudioFrame(payload, timestampUS, codec, sampleRate, channels)
|
frame := c.msgAudioFrame(payload, timestampUS, codec, sampleRate, channels)
|
||||||
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
@@ -273,35 +302,27 @@ func (c *Conn) AVSendAudioData(codec uint16, payload []byte, timestampUS uint32,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(data []byte) error {
|
func (c *DTLSConn) Write(data []byte) error {
|
||||||
// if c.verbose {
|
if c.isCC51 {
|
||||||
// fmt.Printf("[UDP TX] to=%s, len=%d, data:\n%s", c.addr.String(), len(data), hexDump(data))
|
|
||||||
// }
|
|
||||||
|
|
||||||
if c.newProto {
|
|
||||||
_, err := c.conn.WriteToUDP(data, c.addr)
|
_, err := c.conn.WriteToUDP(data, c.addr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err := c.conn.WriteToUDP(crypto.TransCodeBlob(data), c.addr)
|
_, err := c.conn.WriteToUDP(TransCodeBlob(data), c.addr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) WriteDTLS(payload []byte, channel byte) error {
|
func (c *DTLSConn) WriteDTLS(payload []byte, channel byte) error {
|
||||||
var frame []byte
|
var frame []byte
|
||||||
if c.newProto {
|
if c.isCC51 {
|
||||||
frame = c.buildNewTxData(payload, channel)
|
frame = c.msgTxDataCC51(payload, channel)
|
||||||
} else {
|
} else {
|
||||||
frame = c.buildTxData(payload, channel)
|
frame = c.msgTxData(payload, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if c.verbose {
|
|
||||||
// fmt.Printf("[DTLS TX] to=%s, len=%d, channel=%d, data:\n%s", c.addr.String(), len(frame), channel, hexDump(frame))
|
|
||||||
// }
|
|
||||||
|
|
||||||
return c.Write(frame)
|
return c.Write(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) WriteAndWait(req []byte, timeout time.Duration, ok func(res []byte) bool) ([]byte, error) {
|
func (c *DTLSConn) WriteAndWait(req []byte, ok func(res []byte) bool) ([]byte, error) {
|
||||||
var t *time.Timer
|
var t *time.Timer
|
||||||
t = time.AfterFunc(1, func() {
|
t = time.AfterFunc(1, func() {
|
||||||
if err := c.Write(req); err == nil && t != nil {
|
if err := c.Write(req); err == nil && t != nil {
|
||||||
@@ -310,10 +331,10 @@ func (c *Conn) WriteAndWait(req []byte, timeout time.Duration, ok func(res []byt
|
|||||||
})
|
})
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
|
||||||
_ = c.conn.SetDeadline(time.Now().Add(timeout))
|
_ = c.conn.SetDeadline(time.Now().Add(5000 * time.Millisecond))
|
||||||
defer c.conn.SetDeadline(time.Time{})
|
defer c.conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
buf := make([]byte, MaxPacketSize)
|
buf := make([]byte, 2048)
|
||||||
for {
|
for {
|
||||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -324,10 +345,10 @@ func (c *Conn) WriteAndWait(req []byte, timeout time.Duration, ok func(res []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
var res []byte
|
var res []byte
|
||||||
if c.newProto {
|
if c.isCC51 {
|
||||||
res = buf[:n]
|
res = buf[:n]
|
||||||
} else {
|
} else {
|
||||||
res = crypto.ReverseTransCodeBlob(buf[:n])
|
res = ReverseTransCodeBlob(buf[:n])
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok(res) {
|
if ok(res) {
|
||||||
@@ -337,8 +358,8 @@ func (c *Conn) WriteAndWait(req []byte, timeout time.Duration, ok func(res []byt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16, timeout time.Duration) ([]byte, error) {
|
func (c *DTLSConn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16, timeout time.Duration) ([]byte, error) {
|
||||||
frame := c.buildIOCtrlFrame(payload)
|
frame := c.msgIOCtrl(payload)
|
||||||
var t *time.Timer
|
var t *time.Timer
|
||||||
t = time.AfterFunc(1, func() {
|
t = time.AfterFunc(1, func() {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
@@ -362,7 +383,7 @@ func (c *Conn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16,
|
|||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
ack := c.buildACK()
|
ack := c.msgACK()
|
||||||
c.clientConn.Write(ack)
|
c.clientConn.Write(ack)
|
||||||
|
|
||||||
if len(data) >= 6 {
|
if len(data) >= 6 {
|
||||||
@@ -376,29 +397,29 @@ func (c *Conn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) GetAVLoginResponse() *AVLoginResponse {
|
func (c *DTLSConn) HasTwoWayStreaming() bool {
|
||||||
return c.avResp
|
return c.hasTwoWayStreaming
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) IsBackchannelReady() bool {
|
func (c *DTLSConn) IsBackchannelReady() bool {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
return c.serverConn != nil
|
return c.serverConn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) RemoteAddr() *net.UDPAddr {
|
func (c *DTLSConn) RemoteAddr() *net.UDPAddr {
|
||||||
return c.addr
|
return c.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) LocalAddr() *net.UDPAddr {
|
func (c *DTLSConn) LocalAddr() *net.UDPAddr {
|
||||||
return c.conn.LocalAddr().(*net.UDPAddr)
|
return c.conn.LocalAddr().(*net.UDPAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) SetDeadline(t time.Time) error {
|
func (c *DTLSConn) SetDeadline(t time.Time) error {
|
||||||
return c.conn.SetDeadline(t)
|
return c.conn.SetDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
func (c *DTLSConn) Close() error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -416,27 +437,27 @@ func (c *Conn) Close() error {
|
|||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Error() error {
|
func (c *DTLSConn) Error() error {
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return c.err
|
return c.err
|
||||||
}
|
}
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) discovery() error {
|
func (c *DTLSConn) discovery() error {
|
||||||
c.sid = make([]byte, 8)
|
c.sid = GenSessionID()
|
||||||
rand.Read(c.sid)
|
|
||||||
|
|
||||||
oldPkt := crypto.TransCodeBlob(c.buildDisco(1))
|
pktIOTC := TransCodeBlob(c.msgDisco(1))
|
||||||
newPkt := c.buildNewDisco(0, 0, false)
|
pktCC51 := c.msgDiscoCC51(0, 0, false)
|
||||||
buf := make([]byte, MaxPacketSize)
|
|
||||||
deadline := time.Now().Add(DiscoTimeout)
|
buf := make([]byte, 2048)
|
||||||
|
deadline := time.Now().Add(5000 * time.Millisecond)
|
||||||
|
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
c.conn.WriteToUDP(oldPkt, c.addr)
|
c.conn.WriteToUDP(pktIOTC, c.addr)
|
||||||
c.conn.WriteToUDP(newPkt, c.addr)
|
c.conn.WriteToUDP(pktCC51, c.addr)
|
||||||
|
|
||||||
c.conn.SetReadDeadline(time.Now().Add(DiscoInterval))
|
c.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||||
n, addr, err := c.conn.ReadFromUDP(buf)
|
n, addr, err := c.conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -445,59 +466,54 @@ func (c *Conn) discovery() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW protocol
|
// CC51 protocol
|
||||||
if n >= NewPacketSize && binary.LittleEndian.Uint16(buf[:2]) == MagicNewProto {
|
if n >= packetSizeCC51 && string(buf[:2]) == magicCC51 {
|
||||||
if binary.LittleEndian.Uint16(buf[4:]) == CmdNewDisco {
|
if binary.LittleEndian.Uint16(buf[4:]) == cmdDiscoCC51 {
|
||||||
c.addr, c.newProto, c.ticket = addr, true, binary.LittleEndian.Uint16(buf[14:])
|
c.addr, c.isCC51, c.ticket = addr, true, binary.LittleEndian.Uint16(buf[14:])
|
||||||
if n >= 24 {
|
if n >= 24 {
|
||||||
copy(c.sid, buf[16:24])
|
copy(c.sid, buf[16:24])
|
||||||
}
|
}
|
||||||
return c.newDiscoDone()
|
return c.discoDoneCC51()
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// OLD protocol
|
// IOTC Protocol (Basis)
|
||||||
data := crypto.ReverseTransCodeBlob(buf[:n])
|
data := ReverseTransCodeBlob(buf[:n])
|
||||||
if len(data) >= 16 && binary.LittleEndian.Uint16(data[8:]) == CmdDiscoRes {
|
if len(data) >= 16 && binary.LittleEndian.Uint16(data[8:]) == cmdDiscoRes {
|
||||||
c.addr, c.newProto = addr, false
|
c.addr, c.isCC51 = addr, false
|
||||||
return c.oldDiscoDone()
|
return c.discoDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("discovery timeout")
|
return fmt.Errorf("discovery timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) oldDiscoDone() error {
|
func (c *DTLSConn) discoDone() error {
|
||||||
c.Write(c.buildDisco(2))
|
c.Write(c.msgDisco(2))
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
_, err := c.WriteAndWait(c.buildSession(), SessionTimeout, func(res []byte) bool {
|
_, err := c.WriteAndWait(c.msgSession(), func(res []byte) bool {
|
||||||
return len(res) >= 16 && binary.LittleEndian.Uint16(res[8:]) == CmdSessionRes
|
return len(res) >= 16 && binary.LittleEndian.Uint16(res[8:]) == cmdSessionRes
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) newDiscoDone() error {
|
func (c *DTLSConn) discoDoneCC51() error {
|
||||||
_, err := c.WriteAndWait(c.buildNewDisco(2, c.ticket, false), SessionTimeout, func(res []byte) bool {
|
_, err := c.WriteAndWait(c.msgDiscoCC51(2, c.ticket, false), func(res []byte) bool {
|
||||||
if len(res) < NewPacketSize || binary.LittleEndian.Uint16(res[:2]) != MagicNewProto {
|
if len(res) < packetSizeCC51 || string(res[:2]) != magicCC51 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
cmd := binary.LittleEndian.Uint16(res[4:])
|
cmd := binary.LittleEndian.Uint16(res[4:])
|
||||||
dir := binary.LittleEndian.Uint16(res[8:])
|
dir := binary.LittleEndian.Uint16(res[8:])
|
||||||
seq := binary.LittleEndian.Uint16(res[12:])
|
seq := binary.LittleEndian.Uint16(res[12:])
|
||||||
return cmd == CmdNewDisco && dir == 0xFFFF && seq == 3
|
return cmd == cmdDiscoCC51 && dir == 0xFFFF && seq == 3
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) connect() error {
|
func (c *DTLSConn) connect() error {
|
||||||
if c.verbose {
|
adapter := NewChannelAdapter(c.ctx, iotcChannelMain, c.addr, c.WriteDTLS, c.clientBuf)
|
||||||
fmt.Printf("[DTLS] Starting client handshake on channel %d\n", IOTCChannelMain)
|
conn, err := NewDTLSClient(adapter, c.addr, c.psk)
|
||||||
fmt.Printf("[DTLS] PSK Identity: %s\n", PSKIdentity)
|
|
||||||
fmt.Printf("[DTLS] PSK Key: %s\n", hex.EncodeToString(c.psk))
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := NewDtlsClient(c, IOTCChannelMain, c.psk)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dtls: client create failed: %w", err)
|
return fmt.Errorf("dtls: client create failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -507,13 +523,13 @@ func (c *Conn) connect() error {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[DTLS] Client created for channel %d\n", IOTCChannelMain)
|
fmt.Printf("[DTLS] Client created for channel %d\n", iotcChannelMain)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) worker() {
|
func (c *DTLSConn) worker() {
|
||||||
defer c.wg.Done()
|
defer c.wg.Done()
|
||||||
|
|
||||||
buf := make([]byte, 2048)
|
buf := make([]byte, 2048)
|
||||||
@@ -538,15 +554,11 @@ func (c *Conn) worker() {
|
|||||||
data := buf[:n]
|
data := buf[:n]
|
||||||
magic := binary.LittleEndian.Uint16(data)
|
magic := binary.LittleEndian.Uint16(data)
|
||||||
|
|
||||||
// if c.verbose {
|
|
||||||
// fmt.Printf("[DTLS RX] from=%s, len=%d, data:\n%s", c.addr.String(), n, hexDump(data))
|
|
||||||
// }
|
|
||||||
|
|
||||||
switch magic {
|
switch magic {
|
||||||
case MagicAVLoginResp:
|
case magicAVLoginResp:
|
||||||
c.queue(c.rawCmd, data)
|
c.queue(c.rawCmd, data)
|
||||||
|
|
||||||
case MagicIOCtrl:
|
case magicIOCtrl:
|
||||||
if len(data) >= 32 {
|
if len(data) >= 32 {
|
||||||
for i := 32; i+2 < len(data); i++ {
|
for i := 32; i+2 < len(data); i++ {
|
||||||
if data[i] == 'H' && data[i+1] == 'L' {
|
if data[i] == 'H' && data[i+1] == 'L' {
|
||||||
@@ -556,7 +568,7 @@ func (c *Conn) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case MagicChannelMsg:
|
case magicChannelMsg:
|
||||||
if len(data) >= 36 && data[16] == 0x00 {
|
if len(data) >= 36 && data[16] == 0x00 {
|
||||||
for i := 36; i+2 < len(data); i++ {
|
for i := 36; i+2 < len(data); i++ {
|
||||||
if data[i] == 'H' && data[i+1] == 'L' {
|
if data[i] == 'H' && data[i+1] == 'L' {
|
||||||
@@ -566,7 +578,7 @@ func (c *Conn) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ProtoVersion:
|
case protoVersion:
|
||||||
if len(data) >= 8 {
|
if len(data) >= 8 {
|
||||||
// Extract seq number at byte 4-5 (uint16 of uint32 AVSeq)
|
// Extract seq number at byte 4-5 (uint16 of uint32 AVSeq)
|
||||||
seq := binary.LittleEndian.Uint16(data[4:])
|
seq := binary.LittleEndian.Uint16(data[4:])
|
||||||
@@ -589,7 +601,7 @@ func (c *Conn) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case MagicACK:
|
case magicACK:
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
ack := c.cmdAck
|
ack := c.cmdAck
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
@@ -606,9 +618,10 @@ func (c *Conn) worker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) reader() {
|
func (c *DTLSConn) reader() {
|
||||||
defer c.wg.Done()
|
defer c.wg.Done()
|
||||||
buf := make([]byte, MaxPacketSize)
|
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -626,10 +639,6 @@ func (c *Conn) reader() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if c.verbose {
|
|
||||||
// fmt.Printf("[UDP RX] from=%s, len=%d, data:\n%s", addr.String(), n, hexDump(buf[:n]))
|
|
||||||
// }
|
|
||||||
|
|
||||||
if !addr.IP.Equal(c.addr.IP) {
|
if !addr.IP.Equal(c.addr.IP) {
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("Ignored packet from unknown IP: %s\n", addr.IP.String())
|
fmt.Printf("Ignored packet from unknown IP: %s\n", addr.IP.String())
|
||||||
@@ -640,47 +649,47 @@ func (c *Conn) reader() {
|
|||||||
c.addr.Port = addr.Port
|
c.addr.Port = addr.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW protocol (0xCC51)
|
// CC51 Protocol
|
||||||
if c.newProto && n >= 12 && binary.LittleEndian.Uint16(buf[:2]) == MagicNewProto {
|
if c.isCC51 && n >= 12 && string(buf[:2]) == magicCC51 {
|
||||||
cmd := binary.LittleEndian.Uint16(buf[4:])
|
cmd := binary.LittleEndian.Uint16(buf[4:])
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case CmdNewKeepalive:
|
case cmdKeepaliveCC51:
|
||||||
if n >= NewKeepaliveSize {
|
if n >= keepaliveSizeCC51 {
|
||||||
_ = c.Write(c.buildNewKeepalive())
|
_ = c.Write(c.msgKeepaliveCC51())
|
||||||
}
|
}
|
||||||
case CmdNewDTLS:
|
case cmdDTLSCC51:
|
||||||
if n >= NewHeaderSize+NewAuthSize {
|
if n >= headerSizeCC51+authSizeCC51 {
|
||||||
ch := byte(binary.LittleEndian.Uint16(buf[12:]) >> 8)
|
ch := byte(binary.LittleEndian.Uint16(buf[12:]) >> 8)
|
||||||
dtls := buf[NewHeaderSize : n-NewAuthSize]
|
dtlsData := buf[headerSizeCC51 : n-authSizeCC51]
|
||||||
switch ch {
|
switch ch {
|
||||||
case IOTCChannelMain:
|
case iotcChannelMain:
|
||||||
c.queue(c.clientBuf, dtls)
|
c.queue(c.clientBuf, dtlsData)
|
||||||
case IOTCChannelBack:
|
case iotcChannelBack:
|
||||||
c.queue(c.serverBuf, dtls)
|
c.queue(c.serverBuf, dtlsData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// OLD protocol (TransCode)
|
// IOTC Protocol (Basis)
|
||||||
data := crypto.ReverseTransCodeBlob(buf[:n])
|
data := ReverseTransCodeBlob(buf[:n])
|
||||||
if len(data) < 16 {
|
if len(data) < 16 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch binary.LittleEndian.Uint16(data[8:]) {
|
switch binary.LittleEndian.Uint16(data[8:]) {
|
||||||
case CmdKeepaliveRes:
|
case cmdKeepaliveRes:
|
||||||
if len(data) > 24 {
|
if len(data) > 24 {
|
||||||
_ = c.Write(c.buildKeepAlive(data[16:]))
|
_ = c.Write(c.msgKeepalive(data[16:]))
|
||||||
}
|
}
|
||||||
case CmdDataRX:
|
case cmdDataRX:
|
||||||
if len(data) > 28 {
|
if len(data) > 28 {
|
||||||
ch := data[14]
|
ch := data[14]
|
||||||
switch ch {
|
switch ch {
|
||||||
case IOTCChannelMain:
|
case iotcChannelMain:
|
||||||
c.queue(c.clientBuf, data[28:])
|
c.queue(c.clientBuf, data[28:])
|
||||||
case IOTCChannelBack:
|
case iotcChannelBack:
|
||||||
c.queue(c.serverBuf, data[28:])
|
c.queue(c.serverBuf, data[28:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,7 +697,7 @@ func (c *Conn) reader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) queue(ch chan []byte, data []byte) {
|
func (c *DTLSConn) queue(ch chan []byte, data []byte) {
|
||||||
b := make([]byte, len(data))
|
b := make([]byte, len(data))
|
||||||
copy(b, data)
|
copy(b, data)
|
||||||
select {
|
select {
|
||||||
@@ -702,7 +711,7 @@ func (c *Conn) queue(ch chan []byte, data []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) handleSpeakerAVLogin() error {
|
func (c *DTLSConn) handleSpeakerAVLogin() error {
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[SPEAK] Waiting for AV Login request from camera...\n")
|
fmt.Printf("[SPEAK] Waiting for AV Login request from camera...\n")
|
||||||
}
|
}
|
||||||
@@ -723,7 +732,7 @@ func (c *Conn) handleSpeakerAVLogin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checksum := binary.LittleEndian.Uint32(buf[20:])
|
checksum := binary.LittleEndian.Uint32(buf[20:])
|
||||||
resp := c.buildAVLoginResponse(checksum)
|
resp := c.msgAVLoginResponse(checksum)
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[SPEAK] Sending AV Login response: %d bytes\n", len(resp))
|
fmt.Printf("[SPEAK] Sending AV Login response: %d bytes\n", len(resp))
|
||||||
@@ -751,16 +760,16 @@ func (c *Conn) handleSpeakerAVLogin() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildDisco(stage byte) []byte {
|
func (c *DTLSConn) msgDisco(stage byte) []byte {
|
||||||
b := make([]byte, OldDiscoSize)
|
b := make([]byte, discoSize)
|
||||||
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
||||||
binary.LittleEndian.PutUint16(b[4:], OldDiscoBodySize) // body size
|
binary.LittleEndian.PutUint16(b[4:], discoBodySize) // body size
|
||||||
binary.LittleEndian.PutUint16(b[8:], CmdDiscoReq) // 0x0601
|
binary.LittleEndian.PutUint16(b[8:], cmdDiscoReq) // 0x0601
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||||
body := b[OldHeaderSize:]
|
body := b[headerSize:]
|
||||||
copy(body[:UIDSize], c.uid)
|
copy(body[:20], c.uid)
|
||||||
copy(body[36:], "\x01\x01\x02\x04") // unknown
|
copy(body[36:], sdkVersion42) // SDK 4.2.1.1
|
||||||
copy(body[40:], c.rid)
|
copy(body[40:], c.sid)
|
||||||
body[48] = stage
|
body[48] = stage
|
||||||
if stage == 1 && len(c.authKey) > 0 {
|
if stage == 1 && len(c.authKey) > 0 {
|
||||||
copy(body[58:], c.authKey)
|
copy(body[58:], c.authKey)
|
||||||
@@ -768,69 +777,67 @@ func (c *Conn) buildDisco(stage byte) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildNewDisco(seq, ticket uint16, isResponse bool) []byte {
|
func (c *DTLSConn) msgDiscoCC51(seq, ticket uint16, isResponse bool) []byte {
|
||||||
b := make([]byte, NewPacketSize)
|
b := make([]byte, packetSizeCC51)
|
||||||
binary.LittleEndian.PutUint16(b[0:], MagicNewProto) // 0xCC51
|
copy(b[:2], magicCC51)
|
||||||
binary.LittleEndian.PutUint16(b[4:], CmdNewDisco) // 0x1002
|
binary.LittleEndian.PutUint16(b[4:], cmdDiscoCC51) // 0x1002
|
||||||
binary.LittleEndian.PutUint16(b[6:], NewPayloadSize) // 40 bytes
|
binary.LittleEndian.PutUint16(b[6:], payloadSizeCC51) // 40 bytes
|
||||||
if isResponse {
|
if isResponse {
|
||||||
binary.LittleEndian.PutUint16(b[8:], 0xFFFF) // response
|
binary.LittleEndian.PutUint16(b[8:], 0xFFFF) // response
|
||||||
}
|
}
|
||||||
binary.LittleEndian.PutUint16(b[12:], seq)
|
binary.LittleEndian.PutUint16(b[12:], seq)
|
||||||
binary.LittleEndian.PutUint16(b[14:], ticket)
|
binary.LittleEndian.PutUint16(b[14:], ticket)
|
||||||
copy(b[16:24], c.sid)
|
copy(b[16:24], c.sid)
|
||||||
copy(b[24:32], "\x00\x08\x03\x04\x1d\x00\x00\x00") // SDK 4.3.8.0
|
copy(b[24:28], sdkVersion43) // SDK 4.3.8.0
|
||||||
authKey := crypto.CalculateAuthKey(c.enr, c.mac)
|
b[28] = 0x1d // unknown field (capability/build flag?)
|
||||||
h := hmac.New(sha1.New, append([]byte(c.uid), authKey...))
|
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||||
h.Write(b[:32])
|
h.Write(b[:32])
|
||||||
copy(b[32:52], h.Sum(nil))
|
copy(b[32:52], h.Sum(nil))
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildNewKeepalive() []byte {
|
func (c *DTLSConn) msgKeepaliveCC51() []byte {
|
||||||
c.kaSeq += 2
|
c.kaSeq += 2
|
||||||
b := make([]byte, NewKeepaliveSize)
|
b := make([]byte, keepaliveSizeCC51)
|
||||||
binary.LittleEndian.PutUint16(b[0:], MagicNewProto) // 0xCC51
|
copy(b[:2], magicCC51)
|
||||||
binary.LittleEndian.PutUint16(b[4:], CmdNewKeepalive) // 0x1202
|
binary.LittleEndian.PutUint16(b[4:], cmdKeepaliveCC51) // 0x1202
|
||||||
binary.LittleEndian.PutUint16(b[6:], 0x0024) // 36 bytes payload
|
binary.LittleEndian.PutUint16(b[6:], 0x0024) // 36 bytes payload
|
||||||
binary.LittleEndian.PutUint32(b[16:], c.kaSeq) // counter
|
binary.LittleEndian.PutUint32(b[16:], c.kaSeq) // counter
|
||||||
copy(b[20:28], c.sid) // session ID
|
copy(b[20:28], c.sid) // session ID
|
||||||
authKey := crypto.CalculateAuthKey(c.enr, c.mac)
|
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||||
h := hmac.New(sha1.New, append([]byte(c.uid), authKey...))
|
|
||||||
h.Write(b[:28])
|
h.Write(b[:28])
|
||||||
copy(b[28:48], h.Sum(nil))
|
copy(b[28:48], h.Sum(nil))
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildSession() []byte {
|
func (c *DTLSConn) msgSession() []byte {
|
||||||
b := make([]byte, OldSessionSize)
|
b := make([]byte, sessionSize)
|
||||||
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
copy(b, "\x04\x02\x1a\x02") // marker + mode
|
||||||
binary.LittleEndian.PutUint16(b[4:], OldSessionBody) // body size
|
binary.LittleEndian.PutUint16(b[4:], sessionBody) // body size
|
||||||
binary.LittleEndian.PutUint16(b[8:], CmdSessionReq) // 0x0402
|
binary.LittleEndian.PutUint16(b[8:], cmdSessionReq) // 0x0402
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x0033) // flags
|
binary.LittleEndian.PutUint16(b[10:], 0x0033) // flags
|
||||||
body := b[OldHeaderSize:]
|
body := b[headerSize:]
|
||||||
copy(body[:UIDSize], c.uid)
|
copy(body[:20], c.uid)
|
||||||
copy(body[UIDSize:], c.rid)
|
copy(body[20:], c.sid)
|
||||||
binary.LittleEndian.PutUint32(body[32:], uint32(time.Now().Unix()))
|
binary.LittleEndian.PutUint32(body[32:], uint32(time.Now().Unix()))
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildAVLoginPacket(magic uint16, size int, flags uint16, randomID []byte) []byte {
|
func (c *DTLSConn) msgAVLogin(magic uint16, size int, flags uint16, randomID []byte) []byte {
|
||||||
b := make([]byte, size)
|
b := make([]byte, size)
|
||||||
binary.LittleEndian.PutUint16(b, magic)
|
binary.LittleEndian.PutUint16(b, magic)
|
||||||
binary.LittleEndian.PutUint16(b[2:], ProtoVersion)
|
binary.LittleEndian.PutUint16(b[2:], protoVersion)
|
||||||
binary.LittleEndian.PutUint16(b[16:], uint16(size-24)) // payload size
|
binary.LittleEndian.PutUint16(b[16:], uint16(size-24)) // payload size
|
||||||
binary.LittleEndian.PutUint16(b[18:], flags)
|
binary.LittleEndian.PutUint16(b[18:], flags)
|
||||||
copy(b[20:], randomID[:4])
|
copy(b[20:], randomID[:4])
|
||||||
copy(b[24:], DefaultUser) // username
|
copy(b[24:], "admin") // username
|
||||||
copy(b[280:], c.enr) // password/ENR
|
copy(b[280:], c.enr) // password/ENR
|
||||||
// binary.LittleEndian.PutUint32(b[536:], 1) // resend enabled
|
|
||||||
binary.LittleEndian.PutUint32(b[540:], 4) // security_mode ?
|
binary.LittleEndian.PutUint32(b[540:], 4) // security_mode ?
|
||||||
binary.LittleEndian.PutUint32(b[552:], DefaultCaps) // capabilities
|
binary.LittleEndian.PutUint32(b[552:], defaultCaps) // capabilities
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildAVLoginResponse(checksum uint32) []byte {
|
func (c *DTLSConn) msgAVLoginResponse(checksum uint32) []byte {
|
||||||
b := make([]byte, 60)
|
b := make([]byte, 60)
|
||||||
binary.LittleEndian.PutUint16(b, 0x2100) // magic
|
binary.LittleEndian.PutUint16(b, 0x2100) // magic
|
||||||
binary.LittleEndian.PutUint16(b[2:], 0x000c) // version
|
binary.LittleEndian.PutUint16(b[2:], 0x000c) // version
|
||||||
@@ -840,13 +847,13 @@ func (c *Conn) buildAVLoginResponse(checksum uint32) []byte {
|
|||||||
b[29] = 0x01 // enable flag
|
b[29] = 0x01 // enable flag
|
||||||
b[31] = 0x01 // two-way streaming
|
b[31] = 0x01 // two-way streaming
|
||||||
binary.LittleEndian.PutUint32(b[36:], 0x04) // buffer config
|
binary.LittleEndian.PutUint32(b[36:], 0x04) // buffer config
|
||||||
binary.LittleEndian.PutUint32(b[40:], DefaultCaps)
|
binary.LittleEndian.PutUint32(b[40:], defaultCaps)
|
||||||
binary.LittleEndian.PutUint16(b[54:], 0x0003) // channel info
|
binary.LittleEndian.PutUint16(b[54:], 0x0003) // channel info
|
||||||
binary.LittleEndian.PutUint16(b[56:], 0x0002)
|
binary.LittleEndian.PutUint16(b[56:], 0x0002)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildAudioFrame(payload []byte, timestampUS uint32, codec uint16, sampleRate uint32, channels uint8) []byte {
|
func (c *DTLSConn) msgAudioFrame(payload []byte, timestampUS uint32, codec byte, sampleRate uint32, channels uint8) []byte {
|
||||||
c.audioSeq++
|
c.audioSeq++
|
||||||
c.audioFrameNo++
|
c.audioFrameNo++
|
||||||
prevFrame := uint32(0)
|
prevFrame := uint32(0)
|
||||||
@@ -860,7 +867,7 @@ func (c *Conn) buildAudioFrame(payload []byte, timestampUS uint32, codec uint16,
|
|||||||
// Outer header (36 bytes)
|
// Outer header (36 bytes)
|
||||||
b[0] = ChannelAudio // 0x03
|
b[0] = ChannelAudio // 0x03
|
||||||
b[1] = FrameTypeStartAlt // 0x09
|
b[1] = FrameTypeStartAlt // 0x09
|
||||||
binary.LittleEndian.PutUint16(b[2:], ProtoVersion)
|
binary.LittleEndian.PutUint16(b[2:], protoVersion)
|
||||||
binary.LittleEndian.PutUint32(b[4:], c.audioSeq)
|
binary.LittleEndian.PutUint32(b[4:], c.audioSeq)
|
||||||
binary.LittleEndian.PutUint32(b[8:], timestampUS)
|
binary.LittleEndian.PutUint32(b[8:], timestampUS)
|
||||||
if c.audioFrameNo == 1 {
|
if c.audioFrameNo == 1 {
|
||||||
@@ -880,54 +887,65 @@ func (c *Conn) buildAudioFrame(payload []byte, timestampUS uint32, codec uint16,
|
|||||||
binary.LittleEndian.PutUint32(b[32:], c.audioFrameNo)
|
binary.LittleEndian.PutUint32(b[32:], c.audioFrameNo)
|
||||||
copy(b[36:], payload) // Payload + FrameInfo
|
copy(b[36:], payload) // Payload + FrameInfo
|
||||||
fi := b[36+len(payload):]
|
fi := b[36+len(payload):]
|
||||||
binary.LittleEndian.PutUint16(fi, codec)
|
fi[0] = codec // Codec ID (low byte)
|
||||||
fi[2] = BuildAudioFlags(sampleRate, true, channels == 2)
|
fi[1] = 0 // Codec ID (high byte, unused)
|
||||||
|
// Audio flags: [3:2]=sampleRateIdx [1]=16bit [0]=stereo
|
||||||
|
var srIdx uint8 = 3 // default 16kHz
|
||||||
|
for i, rate := range sampleRates {
|
||||||
|
if rate == sampleRate {
|
||||||
|
srIdx = uint8(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fi[2] = (srIdx << 2) | 0x02 // 16-bit always set
|
||||||
|
if channels == 2 {
|
||||||
|
fi[2] |= 0x01
|
||||||
|
}
|
||||||
fi[4] = 1 // online
|
fi[4] = 1 // online
|
||||||
binary.LittleEndian.PutUint32(fi[12:], (c.audioFrameNo-1)*GetSamplesPerFrame(codec)*1000/sampleRate)
|
binary.LittleEndian.PutUint32(fi[12:], (c.audioFrameNo-1)*GetSamplesPerFrame(codec)*1000/sampleRate)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildTxData(payload []byte, channel byte) []byte {
|
func (c *DTLSConn) msgTxData(payload []byte, channel byte) []byte {
|
||||||
bodySize := 12 + len(payload)
|
bodySize := 12 + len(payload)
|
||||||
b := make([]byte, 16+bodySize)
|
b := make([]byte, 16+bodySize)
|
||||||
copy(b, "\x04\x02\x1a\x0b") // marker + mode=data
|
copy(b, "\x04\x02\x1a\x0b") // marker + mode=data
|
||||||
binary.LittleEndian.PutUint16(b[4:], uint16(bodySize)) // body size
|
binary.LittleEndian.PutUint16(b[4:], uint16(bodySize)) // body size
|
||||||
binary.LittleEndian.PutUint16(b[6:], c.seq) // sequence
|
binary.LittleEndian.PutUint16(b[6:], c.seq) // sequence
|
||||||
c.seq++
|
c.seq++
|
||||||
binary.LittleEndian.PutUint16(b[8:], CmdDataTX) // 0x0407
|
binary.LittleEndian.PutUint16(b[8:], cmdDataTX) // 0x0407
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||||
copy(b[12:], c.rid[:2]) // rid[0:2]
|
copy(b[12:], c.sid[:2]) // rid[0:2]
|
||||||
b[14] = channel // channel
|
b[14] = channel // channel
|
||||||
b[15] = 0x01 // marker
|
b[15] = 0x01 // marker
|
||||||
binary.LittleEndian.PutUint32(b[16:], 0x0000000c) // const
|
binary.LittleEndian.PutUint32(b[16:], 0x0000000c) // const
|
||||||
copy(b[20:], c.rid[:8]) // rid
|
copy(b[20:], c.sid[:8]) // rid
|
||||||
copy(b[28:], payload)
|
copy(b[28:], payload)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildNewTxData(payload []byte, channel byte) []byte {
|
func (c *DTLSConn) msgTxDataCC51(payload []byte, channel byte) []byte {
|
||||||
payloadSize := uint16(16 + len(payload) + NewAuthSize)
|
payloadSize := uint16(16 + len(payload) + authSizeCC51)
|
||||||
b := make([]byte, NewHeaderSize+len(payload)+NewAuthSize)
|
b := make([]byte, headerSizeCC51+len(payload)+authSizeCC51)
|
||||||
binary.LittleEndian.PutUint16(b[0:], MagicNewProto) // 0xCC51
|
copy(b[:2], magicCC51)
|
||||||
binary.LittleEndian.PutUint16(b[4:], CmdNewDTLS) // 0x1502
|
binary.LittleEndian.PutUint16(b[4:], cmdDTLSCC51) // 0x1502
|
||||||
binary.LittleEndian.PutUint16(b[6:], payloadSize)
|
binary.LittleEndian.PutUint16(b[6:], payloadSize)
|
||||||
binary.LittleEndian.PutUint16(b[12:], uint16(0x0010)|(uint16(channel)<<8)) // channel in high byte
|
binary.LittleEndian.PutUint16(b[12:], uint16(0x0010)|(uint16(channel)<<8)) // channel in high byte
|
||||||
binary.LittleEndian.PutUint16(b[14:], c.ticket)
|
binary.LittleEndian.PutUint16(b[14:], c.ticket)
|
||||||
copy(b[16:24], c.sid)
|
copy(b[16:24], c.sid)
|
||||||
binary.LittleEndian.PutUint32(b[24:], 1) // const
|
binary.LittleEndian.PutUint32(b[24:], 1) // const
|
||||||
copy(b[NewHeaderSize:], payload)
|
copy(b[headerSizeCC51:], payload)
|
||||||
authKey := crypto.CalculateAuthKey(c.enr, c.mac)
|
h := hmac.New(sha1.New, append([]byte(c.uid), c.authKey...))
|
||||||
h := hmac.New(sha1.New, append([]byte(c.uid), authKey...))
|
h.Write(b[:headerSizeCC51])
|
||||||
h.Write(b[:NewHeaderSize])
|
copy(b[headerSizeCC51+len(payload):], h.Sum(nil))
|
||||||
copy(b[NewHeaderSize+len(payload):], h.Sum(nil))
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildACK() []byte {
|
func (c *DTLSConn) msgACK() []byte {
|
||||||
c.ackFlags++
|
c.ackFlags++
|
||||||
b := make([]byte, 24)
|
b := make([]byte, 24)
|
||||||
binary.LittleEndian.PutUint16(b[0:], MagicACK) // 0x0009
|
binary.LittleEndian.PutUint16(b[0:], magicACK) // 0x0009
|
||||||
binary.LittleEndian.PutUint16(b[2:], ProtoVersion) // 0x000c
|
binary.LittleEndian.PutUint16(b[2:], protoVersion) // 0x000c
|
||||||
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // TX seq
|
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // TX seq
|
||||||
c.avSeq++
|
c.avSeq++
|
||||||
binary.LittleEndian.PutUint16(b[8:], c.rxSeqStart) // RX start (last acked)
|
binary.LittleEndian.PutUint16(b[8:], c.rxSeqStart) // RX start (last acked)
|
||||||
@@ -942,11 +960,11 @@ func (c *Conn) buildACK() []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildKeepAlive(incoming []byte) []byte {
|
func (c *DTLSConn) msgKeepalive(incoming []byte) []byte {
|
||||||
b := make([]byte, 24)
|
b := make([]byte, 24)
|
||||||
copy(b, "\x04\x02\x1a\x0a") // marker + mode
|
copy(b, "\x04\x02\x1a\x0a") // marker + mode
|
||||||
binary.LittleEndian.PutUint16(b[4:], 8) // body size
|
binary.LittleEndian.PutUint16(b[4:], 8) // body size
|
||||||
binary.LittleEndian.PutUint16(b[8:], CmdKeepaliveReq) // 0x0427
|
binary.LittleEndian.PutUint16(b[8:], cmdKeepaliveReq) // 0x0427
|
||||||
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
binary.LittleEndian.PutUint16(b[10:], 0x0021) // flags
|
||||||
if len(incoming) >= 8 {
|
if len(incoming) >= 8 {
|
||||||
copy(b[16:], incoming[:8]) // echo payload
|
copy(b[16:], incoming[:8]) // echo payload
|
||||||
@@ -954,13 +972,13 @@ func (c *Conn) buildKeepAlive(incoming []byte) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) buildIOCtrlFrame(payload []byte) []byte {
|
func (c *DTLSConn) msgIOCtrl(payload []byte) []byte {
|
||||||
b := make([]byte, 40+len(payload))
|
b := make([]byte, 40+len(payload))
|
||||||
binary.LittleEndian.PutUint16(b, ProtoVersion) // magic
|
binary.LittleEndian.PutUint16(b, protoVersion) // magic
|
||||||
binary.LittleEndian.PutUint16(b[2:], ProtoVersion) // version
|
binary.LittleEndian.PutUint16(b[2:], protoVersion) // version
|
||||||
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // av seq
|
binary.LittleEndian.PutUint32(b[4:], c.avSeq) // av seq
|
||||||
c.avSeq++
|
c.avSeq++
|
||||||
binary.LittleEndian.PutUint16(b[16:], MagicIOCtrl) // 0x7000
|
binary.LittleEndian.PutUint16(b[16:], magicIOCtrl) // 0x7000
|
||||||
binary.LittleEndian.PutUint16(b[18:], c.seqCmd) // sub channel
|
binary.LittleEndian.PutUint16(b[18:], c.seqCmd) // sub channel
|
||||||
binary.LittleEndian.PutUint32(b[20:], 1) // ioctl seq
|
binary.LittleEndian.PutUint32(b[20:], 1) // ioctl seq
|
||||||
binary.LittleEndian.PutUint32(b[24:], uint32(len(payload)+4)) // payload size
|
binary.LittleEndian.PutUint32(b[24:], uint32(len(payload)+4)) // payload size
|
||||||
@@ -971,30 +989,6 @@ func (c *Conn) buildIOCtrlFrame(payload []byte) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func derivePSK(enr string) []byte {
|
|
||||||
// TUTK SDK treats the PSK as a NULL-terminated C string, so if SHA256(ENR)
|
|
||||||
// contains a 0x00 byte, the PSK is truncated at that position.
|
|
||||||
// bytes after the first 0x00 are padded with zeros to make a 32-byte key.
|
|
||||||
hash := sha256.Sum256([]byte(enr))
|
|
||||||
pskLen := 32
|
|
||||||
for i := range 32 {
|
|
||||||
if hash[i] == 0x00 {
|
|
||||||
pskLen = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
psk := make([]byte, 32)
|
|
||||||
copy(psk[:pskLen], hash[:pskLen])
|
|
||||||
return psk
|
|
||||||
}
|
|
||||||
|
|
||||||
func genRandomID() []byte {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
_, _ = rand.Read(b)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func hexDump(data []byte) string {
|
func hexDump(data []byte) string {
|
||||||
const maxBytes = 650
|
const maxBytes = 650
|
||||||
totalLen := len(data)
|
totalLen := len(data)
|
||||||
@@ -50,6 +50,34 @@ func ReverseTransCodePartial(dst, src []byte) []byte {
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseTransCodeBlob(src []byte) []byte {
|
||||||
|
if len(src) < 16 {
|
||||||
|
return ReverseTransCodePartial(nil, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, len(src))
|
||||||
|
header := ReverseTransCodePartial(nil, src[:16])
|
||||||
|
copy(dst, header)
|
||||||
|
|
||||||
|
if len(src) > 16 {
|
||||||
|
if dst[3]&1 != 0 { // Partial encryption (check decrypted header)
|
||||||
|
remaining := len(src) - 16
|
||||||
|
decryptLen := min(remaining, 48)
|
||||||
|
if decryptLen > 0 {
|
||||||
|
decrypted := ReverseTransCodePartial(nil, src[16:16+decryptLen])
|
||||||
|
copy(dst[16:], decrypted)
|
||||||
|
}
|
||||||
|
if remaining > 48 {
|
||||||
|
copy(dst[64:], src[64:])
|
||||||
|
}
|
||||||
|
} else { // Full decryption
|
||||||
|
decrypted := ReverseTransCodePartial(nil, src[16:])
|
||||||
|
copy(dst[16:], decrypted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
func TransCodePartial(dst, src []byte) []byte {
|
func TransCodePartial(dst, src []byte) []byte {
|
||||||
n := len(src)
|
n := len(src)
|
||||||
tmp := make([]byte, n)
|
tmp := make([]byte, n)
|
||||||
@@ -92,6 +120,34 @@ func TransCodePartial(dst, src []byte) []byte {
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TransCodeBlob(src []byte) []byte {
|
||||||
|
if len(src) < 16 {
|
||||||
|
return TransCodePartial(nil, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, len(src))
|
||||||
|
header := TransCodePartial(nil, src[:16])
|
||||||
|
copy(dst, header)
|
||||||
|
|
||||||
|
if len(src) > 16 {
|
||||||
|
if src[3]&1 != 0 { // Partial encryption
|
||||||
|
remaining := len(src) - 16
|
||||||
|
encryptLen := min(remaining, 48)
|
||||||
|
if encryptLen > 0 {
|
||||||
|
encrypted := TransCodePartial(nil, src[16:16+encryptLen])
|
||||||
|
copy(dst[16:], encrypted)
|
||||||
|
}
|
||||||
|
if remaining > 48 {
|
||||||
|
copy(dst[64:], src[64:])
|
||||||
|
}
|
||||||
|
} else { // Full encryption
|
||||||
|
encrypted := TransCodePartial(nil, src[16:])
|
||||||
|
copy(dst[16:], encrypted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
func swap(dst, src []byte, n int) {
|
func swap(dst, src []byte, n int) {
|
||||||
switch n {
|
switch n {
|
||||||
case 2:
|
case 2:
|
||||||
@@ -175,3 +231,49 @@ func XXTEADecrypt(dst, src, key []byte) {
|
|||||||
dst = dst[4:]
|
dst = dst[4:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func XXTEADecryptVar(data, key []byte) []byte {
|
||||||
|
if len(data) < 8 || len(key) < 16 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k := make([]uint32, 4)
|
||||||
|
for i := range 4 {
|
||||||
|
k[i] = binary.LittleEndian.Uint32(key[i*4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
n := max(len(data)/4, 2)
|
||||||
|
v := make([]uint32, n)
|
||||||
|
for i := 0; i < len(data)/4; i++ {
|
||||||
|
v[i] = binary.LittleEndian.Uint32(data[i*4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
rounds := 6 + 52/n
|
||||||
|
sum := uint32(rounds) * delta
|
||||||
|
y := v[0]
|
||||||
|
|
||||||
|
for rounds > 0 {
|
||||||
|
e := (sum >> 2) & 3
|
||||||
|
for p := n - 1; p > 0; p-- {
|
||||||
|
z := v[p-1]
|
||||||
|
v[p] -= xxteaMX(sum, y, z, p, e, k)
|
||||||
|
y = v[p]
|
||||||
|
}
|
||||||
|
z := v[n-1]
|
||||||
|
v[0] -= xxteaMX(sum, y, z, 0, e, k)
|
||||||
|
y = v[0]
|
||||||
|
sum -= delta
|
||||||
|
rounds--
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, n*4)
|
||||||
|
for i := range n {
|
||||||
|
binary.LittleEndian.PutUint32(result[i*4:], v[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result[:len(data)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func xxteaMX(sum, y, z uint32, p int, e uint32, k []uint32) uint32 {
|
||||||
|
return ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum ^ y) + (k[(p&3)^int(e)] ^ z))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package tutk
|
package tutk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -8,22 +9,26 @@ import (
|
|||||||
"github.com/pion/dtls/v3"
|
"github.com/pion/dtls/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDtlsClient(c *Conn, channel uint8, psk []byte) (*dtls.Conn, error) {
|
type DTLSConfig struct {
|
||||||
adapter := &ChannelAdapter{conn: c, channel: channel}
|
PSK []byte
|
||||||
return dtls.Client(adapter, c.addr, buildDtlsConfig(psk, false))
|
Identity string
|
||||||
|
IsServer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDtlsServer(c *Conn, channel uint8, psk []byte) (*dtls.Conn, error) {
|
func NewDTLSClient(adapter net.PacketConn, addr net.Addr, psk []byte) (*dtls.Conn, error) {
|
||||||
adapter := &ChannelAdapter{conn: c, channel: channel}
|
return dtls.Client(adapter, addr, buildDTLSConfig(psk, false))
|
||||||
return dtls.Server(adapter, c.addr, buildDtlsConfig(psk, true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDtlsConfig(psk []byte, isServer bool) *dtls.Config {
|
func NewDTLSServer(adapter net.PacketConn, addr net.Addr, psk []byte) (*dtls.Conn, error) {
|
||||||
|
return dtls.Server(adapter, addr, buildDTLSConfig(psk, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDTLSConfig(psk []byte, isServer bool) *dtls.Config {
|
||||||
config := &dtls.Config{
|
config := &dtls.Config{
|
||||||
PSK: func(hint []byte) ([]byte, error) {
|
PSK: func(hint []byte) ([]byte, error) {
|
||||||
return psk, nil
|
return psk, nil
|
||||||
},
|
},
|
||||||
PSKIdentityHint: []byte(PSKIdentity),
|
PSKIdentityHint: []byte("AUTHPWD_admin"),
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
InsecureSkipVerifyHello: true,
|
InsecureSkipVerifyHello: true,
|
||||||
MTU: 1200,
|
MTU: 1200,
|
||||||
@@ -41,21 +46,26 @@ func buildDtlsConfig(psk []byte, isServer bool) *dtls.Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChannelAdapter struct {
|
type ChannelAdapter struct {
|
||||||
conn *Conn
|
ctx context.Context
|
||||||
channel uint8
|
channel uint8
|
||||||
|
writeFn func([]byte, uint8) error
|
||||||
|
readChan chan []byte
|
||||||
|
addr net.Addr
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
readDeadline time.Time
|
readDeadline time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
func NewChannelAdapter(ctx context.Context, channel uint8, addr net.Addr, writeFn func([]byte, uint8) error, readChan chan []byte) *ChannelAdapter {
|
||||||
var buf chan []byte
|
return &ChannelAdapter{
|
||||||
if a.channel == IOTCChannelMain {
|
ctx: ctx,
|
||||||
buf = a.conn.clientBuf
|
channel: channel,
|
||||||
} else {
|
addr: addr,
|
||||||
buf = a.conn.serverBuf
|
writeFn: writeFn,
|
||||||
|
readChan: readChan,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
deadline := a.readDeadline
|
deadline := a.readDeadline
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
@@ -70,25 +80,25 @@ func (a *ChannelAdapter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case data := <-buf:
|
case data := <-a.readChan:
|
||||||
return copy(p, data), a.conn.addr, nil
|
return copy(p, data), a.addr, nil
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return 0, nil, &timeoutError{}
|
return 0, nil, &timeoutError{}
|
||||||
case <-a.conn.ctx.Done():
|
case <-a.ctx.Done():
|
||||||
return 0, nil, net.ErrClosed
|
return 0, nil, net.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case data := <-buf:
|
case data := <-a.readChan:
|
||||||
return copy(p, data), a.conn.addr, nil
|
return copy(p, data), a.addr, nil
|
||||||
case <-a.conn.ctx.Done():
|
case <-a.ctx.Done():
|
||||||
return 0, nil, net.ErrClosed
|
return 0, nil, net.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) WriteTo(p []byte, _ net.Addr) (int, error) {
|
func (a *ChannelAdapter) WriteTo(p []byte, _ net.Addr) (int, error) {
|
||||||
if err := a.conn.WriteDTLS(p, a.channel); err != nil {
|
if err := a.writeFn(p, a.channel); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
@@ -96,21 +106,18 @@ func (a *ChannelAdapter) WriteTo(p []byte, _ net.Addr) (int, error) {
|
|||||||
|
|
||||||
func (a *ChannelAdapter) Close() error { return nil }
|
func (a *ChannelAdapter) Close() error { return nil }
|
||||||
func (a *ChannelAdapter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
func (a *ChannelAdapter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
||||||
|
|
||||||
func (a *ChannelAdapter) SetDeadline(t time.Time) error {
|
func (a *ChannelAdapter) SetDeadline(t time.Time) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
a.readDeadline = t
|
a.readDeadline = t
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) SetReadDeadline(t time.Time) error {
|
func (a *ChannelAdapter) SetReadDeadline(t time.Time) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
a.readDeadline = t
|
a.readDeadline = t
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChannelAdapter) SetWriteDeadline(time.Time) error { return nil }
|
func (a *ChannelAdapter) SetWriteDeadline(time.Time) error { return nil }
|
||||||
|
|
||||||
type timeoutError struct{}
|
type timeoutError struct{}
|
||||||
@@ -25,18 +25,7 @@ const (
|
|||||||
ChannelPVideo uint8 = 0x07
|
ChannelPVideo uint8 = 0x07
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const frameInfoSize = 40
|
||||||
ResTierLow uint8 = 1 // 360P/SD
|
|
||||||
ResTierHigh uint8 = 4 // HD/2K
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bitrate360P uint8 = 30
|
|
||||||
BitrateHD uint8 = 100
|
|
||||||
Bitrate2K uint8 = 200
|
|
||||||
)
|
|
||||||
|
|
||||||
const FrameInfoSize = 40
|
|
||||||
|
|
||||||
// FrameInfo - Wyze extended FRAMEINFO (40 bytes at end of packet)
|
// FrameInfo - Wyze extended FRAMEINFO (40 bytes at end of packet)
|
||||||
// Video: 40 bytes, Audio: 16 bytes (uses same struct, fields 16+ are zero)
|
// Video: 40 bytes, Audio: 16 bytes (uses same struct, fields 16+ are zero)
|
||||||
@@ -56,7 +45,7 @@ const FrameInfoSize = 40
|
|||||||
// 24-35 12 DeviceID - MAC address (ASCII) - video only
|
// 24-35 12 DeviceID - MAC address (ASCII) - video only
|
||||||
// 36-39 4 Padding - Always 0 - video only
|
// 36-39 4 Padding - Always 0 - video only
|
||||||
type FrameInfo struct {
|
type FrameInfo struct {
|
||||||
CodecID uint16 // 0-1
|
CodecID byte // 0 (only low byte used)
|
||||||
Flags uint8 // 2
|
Flags uint8 // 2
|
||||||
CamIndex uint8 // 3
|
CamIndex uint8 // 3
|
||||||
OnlineNum uint8 // 4
|
OnlineNum uint8 // 4
|
||||||
@@ -73,22 +62,12 @@ func (fi *FrameInfo) IsKeyframe() bool {
|
|||||||
return fi.Flags == 0x01
|
return fi.Flags == 0x01
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi *FrameInfo) Resolution() string {
|
|
||||||
switch fi.Bitrate {
|
|
||||||
case Bitrate360P:
|
|
||||||
return "360P"
|
|
||||||
case BitrateHD:
|
|
||||||
return "HD"
|
|
||||||
case Bitrate2K:
|
|
||||||
return "2K"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FrameInfo) SampleRate() uint32 {
|
func (fi *FrameInfo) SampleRate() uint32 {
|
||||||
idx := (fi.Flags >> 2) & 0x0F
|
idx := (fi.Flags >> 2) & 0x0F
|
||||||
return uint32(SampleRateValue(idx))
|
if idx < uint8(len(sampleRates)) {
|
||||||
|
return sampleRates[idx]
|
||||||
|
}
|
||||||
|
return 16000
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi *FrameInfo) Channels() uint8 {
|
func (fi *FrameInfo) Channels() uint8 {
|
||||||
@@ -98,24 +77,16 @@ func (fi *FrameInfo) Channels() uint8 {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi *FrameInfo) IsVideo() bool {
|
|
||||||
return IsVideoCodec(fi.CodecID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi *FrameInfo) IsAudio() bool {
|
|
||||||
return IsAudioCodec(fi.CodecID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseFrameInfo(data []byte) *FrameInfo {
|
func ParseFrameInfo(data []byte) *FrameInfo {
|
||||||
if len(data) < FrameInfoSize {
|
if len(data) < frameInfoSize {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := len(data) - FrameInfoSize
|
offset := len(data) - frameInfoSize
|
||||||
fi := data[offset:]
|
fi := data[offset:]
|
||||||
|
|
||||||
return &FrameInfo{
|
return &FrameInfo{
|
||||||
CodecID: binary.LittleEndian.Uint16(fi),
|
CodecID: fi[0],
|
||||||
Flags: fi[2],
|
Flags: fi[2],
|
||||||
CamIndex: fi[3],
|
CamIndex: fi[3],
|
||||||
OnlineNum: fi[4],
|
OnlineNum: fi[4],
|
||||||
@@ -131,7 +102,7 @@ func ParseFrameInfo(data []byte) *FrameInfo {
|
|||||||
|
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
Channel uint8
|
Channel uint8
|
||||||
Codec uint16
|
Codec byte
|
||||||
Timestamp uint32
|
Timestamp uint32
|
||||||
Payload []byte
|
Payload []byte
|
||||||
IsKeyframe bool
|
IsKeyframe bool
|
||||||
@@ -140,14 +111,6 @@ type Packet struct {
|
|||||||
Channels uint8
|
Channels uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Packet) IsVideo() bool {
|
|
||||||
return p.Channel == ChannelIVideo || p.Channel == ChannelPVideo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Packet) IsAudio() bool {
|
|
||||||
return p.Channel == ChannelAudio
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketHeader struct {
|
type PacketHeader struct {
|
||||||
Channel byte
|
Channel byte
|
||||||
FrameType byte
|
FrameType byte
|
||||||
@@ -347,7 +310,7 @@ func (h *FrameHandler) extractPayload(data []byte, channel byte) ([]byte, *Frame
|
|||||||
frameType := data[1]
|
frameType := data[1]
|
||||||
|
|
||||||
headerSize := 28
|
headerSize := 28
|
||||||
frameInfoSize := 0
|
fiSize := 0
|
||||||
|
|
||||||
switch frameType {
|
switch frameType {
|
||||||
case FrameTypeStart:
|
case FrameTypeStart:
|
||||||
@@ -357,17 +320,17 @@ func (h *FrameHandler) extractPayload(data []byte, channel byte) ([]byte, *Frame
|
|||||||
if len(data) >= 22 {
|
if len(data) >= 22 {
|
||||||
pktTotal := binary.LittleEndian.Uint16(data[20:])
|
pktTotal := binary.LittleEndian.Uint16(data[20:])
|
||||||
if pktTotal == 1 {
|
if pktTotal == 1 {
|
||||||
frameInfoSize = FrameInfoSize
|
fiSize = frameInfoSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case FrameTypeCont, FrameTypeContAlt:
|
case FrameTypeCont, FrameTypeContAlt:
|
||||||
headerSize = 28
|
headerSize = 28
|
||||||
case FrameTypeEndSingle, FrameTypeEndMulti:
|
case FrameTypeEndSingle, FrameTypeEndMulti:
|
||||||
headerSize = 28
|
headerSize = 28
|
||||||
frameInfoSize = FrameInfoSize
|
fiSize = frameInfoSize
|
||||||
case FrameTypeEndExt:
|
case FrameTypeEndExt:
|
||||||
headerSize = 36
|
headerSize = 36
|
||||||
frameInfoSize = FrameInfoSize
|
fiSize = frameInfoSize
|
||||||
default:
|
default:
|
||||||
headerSize = 28
|
headerSize = 28
|
||||||
}
|
}
|
||||||
@@ -376,11 +339,11 @@ func (h *FrameHandler) extractPayload(data []byte, channel byte) ([]byte, *Frame
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if frameInfoSize == 0 {
|
if fiSize == 0 {
|
||||||
return data[headerSize:], nil
|
return data[headerSize:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < headerSize+frameInfoSize {
|
if len(data) < headerSize+fiSize {
|
||||||
return data[headerSize:], nil
|
return data[headerSize:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +358,7 @@ func (h *FrameHandler) extractPayload(data []byte, channel byte) ([]byte, *Frame
|
|||||||
}
|
}
|
||||||
|
|
||||||
if validCodec {
|
if validCodec {
|
||||||
payload := data[headerSize : len(data)-frameInfoSize]
|
payload := data[headerSize : len(data)-fiSize]
|
||||||
return payload, fi
|
return payload, fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +384,7 @@ func (h *FrameHandler) handleVideo(channel byte, hdr *PacketHeader, payload []by
|
|||||||
cs.pktTotal = hdr.PktTotal
|
cs.pktTotal = hdr.PktTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequential check: if packet index doesn't match expected, reset (data loss)
|
// If packet index doesn't match expected, reset (data loss)
|
||||||
if hdr.PktIdx != cs.waitSeq {
|
if hdr.PktIdx != cs.waitSeq {
|
||||||
fmt.Printf("[OOO] ch=0x%02x #%d frameType=0x%02x pktTotal=%d expected pkt %d, got %d - reset\n",
|
fmt.Printf("[OOO] ch=0x%02x #%d frameType=0x%02x pktTotal=%d expected pkt %d, got %d - reset\n",
|
||||||
channel, hdr.FrameNo, hdr.FrameType, hdr.PktTotal, cs.waitSeq, hdr.PktIdx)
|
channel, hdr.FrameNo, hdr.FrameType, hdr.PktTotal, cs.waitSeq, hdr.PktIdx)
|
||||||
@@ -434,7 +397,6 @@ func (h *FrameHandler) handleVideo(channel byte, hdr *PacketHeader, payload []by
|
|||||||
cs.hasStarted = true
|
cs.hasStarted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append payload (simple sequential accumulation)
|
|
||||||
cs.waitData = append(cs.waitData, payload...)
|
cs.waitData = append(cs.waitData, payload...)
|
||||||
cs.waitSeq++
|
cs.waitSeq++
|
||||||
|
|
||||||
@@ -444,16 +406,13 @@ func (h *FrameHandler) handleVideo(channel byte, hdr *PacketHeader, payload []by
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if frame is complete
|
// Check if frame is complete
|
||||||
if cs.waitSeq == cs.pktTotal && cs.frameInfo != nil {
|
if cs.waitSeq != cs.pktTotal || cs.frameInfo == nil {
|
||||||
h.emitVideo(channel, cs)
|
return
|
||||||
cs.reset()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FrameHandler) emitVideo(channel byte, cs *channelState) {
|
fi = cs.frameInfo
|
||||||
fi := cs.frameInfo
|
defer cs.reset()
|
||||||
|
|
||||||
// Size validation
|
|
||||||
if fi.PayloadSize > 0 && uint32(len(cs.waitData)) != fi.PayloadSize {
|
if fi.PayloadSize > 0 && uint32(len(cs.waitData)) != fi.PayloadSize {
|
||||||
fmt.Printf("[SIZE] ch=0x%02x #%d mismatch: expected %d, got %d\n",
|
fmt.Printf("[SIZE] ch=0x%02x #%d mismatch: expected %d, got %d\n",
|
||||||
channel, cs.frameNo, fi.PayloadSize, len(cs.waitData))
|
channel, cs.frameNo, fi.PayloadSize, len(cs.waitData))
|
||||||
@@ -467,13 +426,9 @@ func (h *FrameHandler) emitVideo(channel byte, cs *channelState) {
|
|||||||
accumUS := h.videoTS.update(fi.Timestamp)
|
accumUS := h.videoTS.update(fi.Timestamp)
|
||||||
rtpTS := uint32(accumUS * 90000 / 1000000)
|
rtpTS := uint32(accumUS * 90000 / 1000000)
|
||||||
|
|
||||||
// Copy payload (buffer will be reused)
|
|
||||||
payload := make([]byte, len(cs.waitData))
|
|
||||||
copy(payload, cs.waitData)
|
|
||||||
|
|
||||||
pkt := &Packet{
|
pkt := &Packet{
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
Payload: payload,
|
Payload: append([]byte{}, cs.waitData...),
|
||||||
Codec: fi.CodecID,
|
Codec: fi.CodecID,
|
||||||
Timestamp: rtpTS,
|
Timestamp: rtpTS,
|
||||||
IsKeyframe: fi.IsKeyframe(),
|
IsKeyframe: fi.IsKeyframe(),
|
||||||
@@ -485,10 +440,10 @@ func (h *FrameHandler) emitVideo(channel byte, cs *channelState) {
|
|||||||
if fi.IsKeyframe() {
|
if fi.IsKeyframe() {
|
||||||
frameType = "KEY"
|
frameType = "KEY"
|
||||||
}
|
}
|
||||||
fmt.Printf("[OK] ch=0x%02x #%d %s %s size=%d\n",
|
fmt.Printf("[OK] ch=0x%02x #%d codec=0x%02x %s size=%d\n",
|
||||||
channel, fi.FrameNo, CodecName(fi.CodecID), frameType, len(payload))
|
channel, fi.FrameNo, fi.CodecID, frameType, len(pkt.Payload))
|
||||||
fmt.Printf(" [0-1]codec=0x%x(%s) [2]flags=0x%x [3]=%d [4]=%d\n",
|
fmt.Printf(" [0-1]codec=0x%02x [2]flags=0x%x [3]=%d [4]=%d\n",
|
||||||
fi.CodecID, CodecName(fi.CodecID), fi.Flags, fi.CamIndex, fi.OnlineNum)
|
fi.CodecID, fi.Flags, fi.CamIndex, fi.OnlineNum)
|
||||||
fmt.Printf(" [5]=%d [6]=%d [7]=%d [8-11]ts=%d\n",
|
fmt.Printf(" [5]=%d [6]=%d [7]=%d [8-11]ts=%d\n",
|
||||||
fi.FPS, fi.ResTier, fi.Bitrate, fi.Timestamp)
|
fi.FPS, fi.ResTier, fi.Bitrate, fi.Timestamp)
|
||||||
fmt.Printf(" [12-15]=0x%x [16-19]payload=%d [20-23]frameNo=%d\n",
|
fmt.Printf(" [12-15]=0x%x [16-19]payload=%d [20-23]frameNo=%d\n",
|
||||||
@@ -509,7 +464,7 @@ func (h *FrameHandler) handleAudio(payload []byte, fi *FrameInfo) {
|
|||||||
var channels uint8
|
var channels uint8
|
||||||
|
|
||||||
switch fi.CodecID {
|
switch fi.CodecID {
|
||||||
case AudioCodecAACRaw, AudioCodecAACADTS, AudioCodecAACLATM, AudioCodecAACWyze:
|
case CodecAACRaw, CodecAACADTS, CodecAACLATM, CodecAACAlt:
|
||||||
sampleRate, channels = parseAudioParams(payload, fi)
|
sampleRate, channels = parseAudioParams(payload, fi)
|
||||||
default:
|
default:
|
||||||
sampleRate = fi.SampleRate()
|
sampleRate = fi.SampleRate()
|
||||||
@@ -537,10 +492,10 @@ func (h *FrameHandler) handleAudio(payload []byte, fi *FrameInfo) {
|
|||||||
if fi.Flags&0x02 != 0 {
|
if fi.Flags&0x02 != 0 {
|
||||||
bits = 16
|
bits = 16
|
||||||
}
|
}
|
||||||
fmt.Printf("[OK] Audio #%d %s size=%d\n",
|
fmt.Printf("[OK] Audio #%d codec=0x%02x size=%d\n",
|
||||||
fi.FrameNo, AudioCodecName(fi.CodecID), len(payload))
|
fi.FrameNo, fi.CodecID, len(payload))
|
||||||
fmt.Printf(" [0-1]codec=0x%x(%s) [2]flags=0x%x(%dHz/%dbit/%dch)\n",
|
fmt.Printf(" [0-1]codec=0x%02x [2]flags=0x%x(%dHz/%dbit/%dch)\n",
|
||||||
fi.CodecID, AudioCodecName(fi.CodecID), fi.Flags, sampleRate, bits, channels)
|
fi.CodecID, fi.Flags, sampleRate, bits, channels)
|
||||||
fmt.Printf(" [8-11]ts=%d [12-15]=0x%x rtp_ts=%d\n",
|
fmt.Printf(" [8-11]ts=%d [12-15]=0x%x rtp_ts=%d\n",
|
||||||
fi.Timestamp, fi.SessionID, rtpTS)
|
fi.Timestamp, fi.SessionID, rtpTS)
|
||||||
fmt.Printf(" hex: %s\n", dumpHex(fi))
|
fmt.Printf(" hex: %s\n", dumpHex(fi))
|
||||||
@@ -589,8 +544,9 @@ func parseAudioParams(payload []byte, fi *FrameInfo) (sampleRate uint32, channel
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dumpHex(fi *FrameInfo) string {
|
func dumpHex(fi *FrameInfo) string {
|
||||||
b := make([]byte, FrameInfoSize)
|
b := make([]byte, frameInfoSize)
|
||||||
binary.LittleEndian.PutUint16(b[0:], fi.CodecID)
|
b[0] = fi.CodecID
|
||||||
|
b[1] = 0 // High byte (unused)
|
||||||
b[2] = fi.Flags
|
b[2] = fi.Flags
|
||||||
b[3] = fi.CamIndex
|
b[3] = fi.CamIndex
|
||||||
b[4] = fi.OnlineNum
|
b[4] = fi.OnlineNum
|
||||||
+43
-9
@@ -1,16 +1,16 @@
|
|||||||
package tutk
|
package tutk
|
||||||
|
|
||||||
import "encoding/binary"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
// https://github.com/seydx/tutk_wyze#11-codec-reference
|
"time"
|
||||||
const (
|
|
||||||
CodecH264 = 0x4e
|
|
||||||
CodecH265 = 0x50
|
|
||||||
CodecPCMA = 0x8a
|
|
||||||
CodecPCML = 0x8c
|
|
||||||
CodecAAC = 0x88
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GenSessionID() []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(b, uint64(time.Now().UnixNano()))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func ICAM(cmd uint32, args ...byte) []byte {
|
func ICAM(cmd uint32, args ...byte) []byte {
|
||||||
// 0 4943414d ICAM
|
// 0 4943414d ICAM
|
||||||
// 4 d807ff00 command
|
// 4 d807ff00 command
|
||||||
@@ -26,3 +26,37 @@ func ICAM(cmd uint32, args ...byte) []byte {
|
|||||||
copy(b[23:], args)
|
copy(b[23:], args)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HL(cmdID uint16, payload []byte) []byte {
|
||||||
|
// 0-1 "HL" magic
|
||||||
|
// 2 version (typically 5)
|
||||||
|
// 3 reserved
|
||||||
|
// 4-5 cmdID command ID (uint16 LE)
|
||||||
|
// 6-7 payloadLen payload length (uint16 LE)
|
||||||
|
// 8-15 reserved
|
||||||
|
// 16+ payload
|
||||||
|
const headerSize = 16
|
||||||
|
const version = 5
|
||||||
|
|
||||||
|
b := make([]byte, headerSize+len(payload))
|
||||||
|
copy(b, "HL")
|
||||||
|
b[2] = version
|
||||||
|
binary.LittleEndian.PutUint16(b[4:], cmdID)
|
||||||
|
binary.LittleEndian.PutUint16(b[6:], uint16(len(payload)))
|
||||||
|
copy(b[headerSize:], payload)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseHL(data []byte) (cmdID uint16, payload []byte, ok bool) {
|
||||||
|
if len(data) < 16 || data[0] != 'H' || data[1] != 'L' {
|
||||||
|
return 0, nil, false
|
||||||
|
}
|
||||||
|
cmdID = binary.LittleEndian.Uint16(data[4:])
|
||||||
|
payloadLen := binary.LittleEndian.Uint16(data[6:])
|
||||||
|
if len(data) >= 16+int(payloadLen) {
|
||||||
|
payload = data[16 : 16+payloadLen]
|
||||||
|
} else if len(data) > 16 {
|
||||||
|
payload = data[16:]
|
||||||
|
}
|
||||||
|
return cmdID, payload, true
|
||||||
|
}
|
||||||
|
|||||||
@@ -155,9 +155,3 @@ func ConnectByUID(stage byte, uid string, sid8 []byte) []byte {
|
|||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenSessionID() []byte {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.LittleEndian.PutUint64(b, uint64(time.Now().UnixNano()))
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/aac"
|
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/wyze/tutk"
|
"github.com/AlexxIT/go2rtc/pkg/tutk"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+50
-23
@@ -12,8 +12,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/wyze/crypto"
|
"github.com/AlexxIT/go2rtc/pkg/tutk"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/wyze/tutk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -29,15 +28,6 @@ const (
|
|||||||
BitrateSD uint16 = 0x3C
|
BitrateSD uint16 = 0x3C
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
QualityUnknown = 0
|
|
||||||
QualityMax = 1
|
|
||||||
QualityHigh = 2
|
|
||||||
QualityMiddle = 3
|
|
||||||
QualityLow = 4
|
|
||||||
QualityMin = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MediaTypeVideo = 1
|
MediaTypeVideo = 1
|
||||||
MediaTypeAudio = 2
|
MediaTypeAudio = 2
|
||||||
@@ -59,7 +49,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
conn *tutk.Conn
|
conn *tutk.DTLSConn
|
||||||
|
|
||||||
host string
|
host string
|
||||||
uid string
|
uid string
|
||||||
@@ -76,7 +66,7 @@ type Client struct {
|
|||||||
hasAudio bool
|
hasAudio bool
|
||||||
hasIntercom bool
|
hasIntercom bool
|
||||||
|
|
||||||
audioCodecID uint16
|
audioCodecID byte
|
||||||
audioSampleRate uint32
|
audioSampleRate uint32
|
||||||
audioChannels uint8
|
audioChannels uint8
|
||||||
}
|
}
|
||||||
@@ -107,7 +97,7 @@ func Dial(rawURL string) (*Client, error) {
|
|||||||
verbose: query.Get("verbose") == "true",
|
verbose: query.Get("verbose") == "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
c.authKey = string(crypto.CalculateAuthKey(c.enr, c.mac))
|
c.authKey = string(tutk.CalculateAuthKey(c.enr, c.mac))
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[Wyze] Connecting to %s (UID: %s)\n", c.host, c.uid)
|
fmt.Printf("[Wyze] Connecting to %s (UID: %s)\n", c.host, c.uid)
|
||||||
@@ -143,13 +133,13 @@ func (c *Client) SupportsIntercom() bool {
|
|||||||
return c.hasIntercom
|
return c.hasIntercom
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetBackchannelCodec(codecID uint16, sampleRate uint32, channels uint8) {
|
func (c *Client) SetBackchannelCodec(codecID byte, sampleRate uint32, channels uint8) {
|
||||||
c.audioCodecID = codecID
|
c.audioCodecID = codecID
|
||||||
c.audioSampleRate = sampleRate
|
c.audioSampleRate = sampleRate
|
||||||
c.audioChannels = channels
|
c.audioChannels = channels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetBackchannelCodec() (codecID uint16, sampleRate uint32, channels uint8) {
|
func (c *Client) GetBackchannelCodec() (codecID byte, sampleRate uint32, channels uint8) {
|
||||||
return c.audioCodecID, c.audioSampleRate, c.audioChannels
|
return c.audioCodecID, c.audioSampleRate, c.audioChannels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,13 +228,13 @@ func (c *Client) ReadPacket() (*tutk.Packet, error) {
|
|||||||
return c.conn.AVRecvFrameData()
|
return c.conn.AVRecvFrameData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) WriteAudio(codec uint16, payload []byte, timestamp uint32, sampleRate uint32, channels uint8) error {
|
func (c *Client) WriteAudio(codec byte, payload []byte, timestamp uint32, sampleRate uint32, channels uint8) error {
|
||||||
if !c.conn.IsBackchannelReady() {
|
if !c.conn.IsBackchannelReady() {
|
||||||
return fmt.Errorf("speaker channel not connected")
|
return fmt.Errorf("speaker channel not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[Wyze] WriteAudio: codec=0x%04x, payload=%d bytes, rate=%d, ch=%d\n", codec, len(payload), sampleRate, channels)
|
fmt.Printf("[Wyze] WriteAudio: codec=0x%02x, payload=%d bytes, rate=%d, ch=%d\n", codec, len(payload), sampleRate, channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.conn.AVSendAudioData(codec, payload, timestamp, sampleRate, channels)
|
return c.conn.AVSendAudioData(codec, payload, timestamp, sampleRate, channels)
|
||||||
@@ -305,7 +295,7 @@ func (c *Client) connect() error {
|
|||||||
host = host[:idx]
|
host = host[:idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := tutk.Dial(host, port, c.uid, c.authKey, c.enr, c.mac, c.verbose)
|
conn, err := tutk.DialDTLS(host, port, c.uid, c.authKey, c.enr, c.verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wyze: connect failed: %w", err)
|
return fmt.Errorf("wyze: connect failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -386,9 +376,7 @@ func (c *Client) doKAuth() error {
|
|||||||
fmt.Printf("[Wyze] K10003 auth success\n")
|
fmt.Printf("[Wyze] K10003 auth success\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if avResp := c.conn.GetAVLoginResponse(); avResp != nil {
|
c.hasIntercom = c.conn.HasTwoWayStreaming()
|
||||||
c.hasIntercom = avResp.TwoWayStreaming == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
fmt.Printf("[Wyze] K-auth complete\n")
|
fmt.Printf("[Wyze] K-auth complete\n")
|
||||||
@@ -409,7 +397,7 @@ func (c *Client) buildK10000() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildK10002(challenge []byte, status byte) []byte {
|
func (c *Client) buildK10002(challenge []byte, status byte) []byte {
|
||||||
resp := crypto.GenerateChallengeResponse(challenge, c.enr, status)
|
resp := generateChallengeResponse(challenge, c.enr, status)
|
||||||
sessionID := make([]byte, 4)
|
sessionID := make([]byte, 4)
|
||||||
rand.Read(sessionID)
|
rand.Read(sessionID)
|
||||||
b := make([]byte, 38)
|
b := make([]byte, 38)
|
||||||
@@ -555,3 +543,42 @@ func (c *Client) is2K() bool {
|
|||||||
func (c *Client) isFloodlight() bool {
|
func (c *Client) isFloodlight() bool {
|
||||||
return c.model == "HL_CFL2"
|
return c.model == "HL_CFL2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusDefault byte = 1
|
||||||
|
statusENR16 byte = 3
|
||||||
|
statusENR32 byte = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateChallengeResponse(challengeBytes []byte, enr string, status byte) []byte {
|
||||||
|
var secretKey []byte
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case statusDefault:
|
||||||
|
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
||||||
|
case statusENR16:
|
||||||
|
if len(enr) >= 16 {
|
||||||
|
secretKey = []byte(enr[:16])
|
||||||
|
} else {
|
||||||
|
secretKey = make([]byte, 16)
|
||||||
|
copy(secretKey, enr)
|
||||||
|
}
|
||||||
|
case statusENR32:
|
||||||
|
if len(enr) >= 16 {
|
||||||
|
firstKey := []byte(enr[:16])
|
||||||
|
challengeBytes = tutk.XXTEADecryptVar(challengeBytes, firstKey)
|
||||||
|
}
|
||||||
|
if len(enr) >= 32 {
|
||||||
|
secretKey = []byte(enr[16:32])
|
||||||
|
} else if len(enr) > 16 {
|
||||||
|
secretKey = make([]byte, 16)
|
||||||
|
copy(secretKey, []byte(enr[16:]))
|
||||||
|
} else {
|
||||||
|
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tutk.XXTEADecryptVar(challengeBytes, secretKey)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
const charlie = "Charlie is the designer of P2P!!"
|
|
||||||
|
|
||||||
func TransCodePartial(src []byte) []byte {
|
|
||||||
n := len(src)
|
|
||||||
tmp := make([]byte, n)
|
|
||||||
dst := bytes.Clone(src)
|
|
||||||
src16, tmp16, dst16 := src, tmp, 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 := range 16 {
|
|
||||||
dst16[i] = tmp16[i] ^ charlie[i]
|
|
||||||
}
|
|
||||||
swap(dst16, tmp16, 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, dst16, src16 = tmp16[16:], dst16[16:], src16[16:]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
tmp16[i] = src16[i] ^ charlie[i]
|
|
||||||
}
|
|
||||||
swap(tmp16, dst16, n)
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReverseTransCodePartial(src []byte) []byte {
|
|
||||||
n := len(src)
|
|
||||||
tmp := make([]byte, n)
|
|
||||||
dst := bytes.Clone(src)
|
|
||||||
src16, tmp16, dst16 := src, tmp, 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(tmp16, dst16, 16)
|
|
||||||
for i := range 16 {
|
|
||||||
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, dst16, src16 = tmp16[16:], dst16[16:], src16[16:]
|
|
||||||
}
|
|
||||||
|
|
||||||
swap(src16, tmp16, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
dst16[i] = tmp16[i] ^ charlie[i]
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func TransCodeBlob(src []byte) []byte {
|
|
||||||
if len(src) < 16 {
|
|
||||||
return TransCodePartial(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := make([]byte, len(src))
|
|
||||||
header := TransCodePartial(src[:16])
|
|
||||||
copy(dst, header)
|
|
||||||
|
|
||||||
if len(src) > 16 {
|
|
||||||
if src[3]&1 != 0 { // Partial encryption
|
|
||||||
remaining := len(src) - 16
|
|
||||||
encryptLen := min(remaining, 48)
|
|
||||||
if encryptLen > 0 {
|
|
||||||
encrypted := TransCodePartial(src[16 : 16+encryptLen])
|
|
||||||
copy(dst[16:], encrypted)
|
|
||||||
}
|
|
||||||
if remaining > 48 {
|
|
||||||
copy(dst[64:], src[64:])
|
|
||||||
}
|
|
||||||
} else { // Full encryption
|
|
||||||
encrypted := TransCodePartial(src[16:])
|
|
||||||
copy(dst[16:], encrypted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReverseTransCodeBlob(src []byte) []byte {
|
|
||||||
if len(src) < 16 {
|
|
||||||
return ReverseTransCodePartial(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := make([]byte, len(src))
|
|
||||||
header := ReverseTransCodePartial(src[:16])
|
|
||||||
copy(dst, header)
|
|
||||||
|
|
||||||
if len(src) > 16 {
|
|
||||||
if dst[3]&1 != 0 { // Partial encryption (check decrypted header)
|
|
||||||
remaining := len(src) - 16
|
|
||||||
decryptLen := min(remaining, 48)
|
|
||||||
if decryptLen > 0 {
|
|
||||||
decrypted := ReverseTransCodePartial(src[16 : 16+decryptLen])
|
|
||||||
copy(dst[16:], decrypted)
|
|
||||||
}
|
|
||||||
if remaining > 48 {
|
|
||||||
copy(dst[64:], src[64:])
|
|
||||||
}
|
|
||||||
} else { // Full decryption
|
|
||||||
decrypted := ReverseTransCodePartial(src[16:])
|
|
||||||
copy(dst[16:], decrypted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func RandRead(b []byte) {
|
|
||||||
_, _ = rand.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func swap(src, dst []byte, n int) {
|
|
||||||
switch n {
|
|
||||||
case 8:
|
|
||||||
dst[0], dst[1], dst[2], dst[3] = src[7], src[4], src[3], src[2]
|
|
||||||
dst[4], dst[5], dst[6], dst[7] = src[1], src[6], src[5], src[0]
|
|
||||||
case 16:
|
|
||||||
dst[0], dst[1], dst[2], dst[3] = src[11], src[9], src[8], src[15]
|
|
||||||
dst[4], dst[5], dst[6], dst[7] = src[13], src[10], src[12], src[14]
|
|
||||||
dst[8], dst[9], dst[10], dst[11] = src[2], src[1], src[5], src[0]
|
|
||||||
dst[12], dst[13], dst[14], dst[15] = src[6], src[4], src[7], src[3]
|
|
||||||
default:
|
|
||||||
copy(dst, src[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const delta = 0x9e3779b9
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusDefault byte = 1
|
|
||||||
StatusENR16 byte = 3
|
|
||||||
StatusENR32 byte = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
func XXTEADecrypt(data, key []byte) []byte {
|
|
||||||
if len(data) < 8 || len(key) < 16 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
k := make([]uint32, 4)
|
|
||||||
for i := range 4 {
|
|
||||||
k[i] = binary.LittleEndian.Uint32(key[i*4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
n := max(len(data)/4, 2)
|
|
||||||
v := make([]uint32, n)
|
|
||||||
for i := 0; i < len(data)/4; i++ {
|
|
||||||
v[i] = binary.LittleEndian.Uint32(data[i*4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
rounds := 6 + 52/n
|
|
||||||
sum := uint32(rounds) * delta
|
|
||||||
y := v[0]
|
|
||||||
|
|
||||||
for rounds > 0 {
|
|
||||||
e := (sum >> 2) & 3
|
|
||||||
for p := n - 1; p > 0; p-- {
|
|
||||||
z := v[p-1]
|
|
||||||
v[p] -= mx(sum, y, z, p, e, k)
|
|
||||||
y = v[p]
|
|
||||||
}
|
|
||||||
z := v[n-1]
|
|
||||||
v[0] -= mx(sum, y, z, 0, e, k)
|
|
||||||
y = v[0]
|
|
||||||
sum -= delta
|
|
||||||
rounds--
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]byte, n*4)
|
|
||||||
for i := range n {
|
|
||||||
binary.LittleEndian.PutUint32(result[i*4:], v[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[:len(data)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func XXTEAEncrypt(data, key []byte) []byte {
|
|
||||||
if len(data) < 8 || len(key) < 16 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
k := make([]uint32, 4)
|
|
||||||
for i := range 4 {
|
|
||||||
k[i] = binary.LittleEndian.Uint32(key[i*4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
n := max(len(data)/4, 2)
|
|
||||||
v := make([]uint32, n)
|
|
||||||
for i := 0; i < len(data)/4; i++ {
|
|
||||||
v[i] = binary.LittleEndian.Uint32(data[i*4:])
|
|
||||||
}
|
|
||||||
|
|
||||||
rounds := 6 + 52/n
|
|
||||||
var sum uint32
|
|
||||||
z := v[n-1]
|
|
||||||
|
|
||||||
for rounds > 0 {
|
|
||||||
sum += delta
|
|
||||||
e := (sum >> 2) & 3
|
|
||||||
for p := 0; p < n-1; p++ {
|
|
||||||
y := v[p+1]
|
|
||||||
v[p] += mx(sum, y, z, p, e, k)
|
|
||||||
z = v[p]
|
|
||||||
}
|
|
||||||
y := v[0]
|
|
||||||
v[n-1] += mx(sum, y, z, n-1, e, k)
|
|
||||||
z = v[n-1]
|
|
||||||
rounds--
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]byte, n*4)
|
|
||||||
for i := range n {
|
|
||||||
binary.LittleEndian.PutUint32(result[i*4:], v[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[:len(data)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func mx(sum, y, z uint32, p int, e uint32, k []uint32) uint32 {
|
|
||||||
return ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum ^ y) + (k[(p&3)^int(e)] ^ z))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateChallengeResponse(challengeBytes []byte, enr string, status byte) []byte {
|
|
||||||
var secretKey []byte
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case StatusDefault:
|
|
||||||
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
|
||||||
case StatusENR16:
|
|
||||||
if len(enr) >= 16 {
|
|
||||||
secretKey = []byte(enr[:16])
|
|
||||||
} else {
|
|
||||||
secretKey = make([]byte, 16)
|
|
||||||
copy(secretKey, enr)
|
|
||||||
}
|
|
||||||
case StatusENR32:
|
|
||||||
if len(enr) >= 16 {
|
|
||||||
firstKey := []byte(enr[:16])
|
|
||||||
challengeBytes = XXTEADecrypt(challengeBytes, firstKey)
|
|
||||||
}
|
|
||||||
if len(enr) >= 32 {
|
|
||||||
secretKey = []byte(enr[16:32])
|
|
||||||
} else if len(enr) > 16 {
|
|
||||||
secretKey = make([]byte, 16)
|
|
||||||
copy(secretKey, []byte(enr[16:]))
|
|
||||||
} else {
|
|
||||||
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
secretKey = []byte("FFFFFFFFFFFFFFFF")
|
|
||||||
}
|
|
||||||
|
|
||||||
return XXTEADecrypt(challengeBytes, secretKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CalculateAuthKey(enr, mac string) []byte {
|
|
||||||
data := enr + strings.ToUpper(mac)
|
|
||||||
hash := sha256.Sum256([]byte(data))
|
|
||||||
b64 := base64.StdEncoding.EncodeToString(hash[:6])
|
|
||||||
b64 = strings.ReplaceAll(b64, "+", "Z")
|
|
||||||
b64 = strings.ReplaceAll(b64, "/", "9")
|
|
||||||
b64 = strings.ReplaceAll(b64, "=", "A")
|
|
||||||
return []byte(b64)
|
|
||||||
}
|
|
||||||
+14
-14
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/wyze/tutk"
|
"github.com/AlexxIT/go2rtc/pkg/tutk"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,21 +96,21 @@ func (p *Producer) Start() error {
|
|||||||
Payload: annexb.EncodeToAVCC(pkt.Payload),
|
Payload: annexb.EncodeToAVCC(pkt.Payload),
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecG711U:
|
case tutk.CodecPCMU:
|
||||||
name = core.CodecPCMU
|
name = core.CodecPCMU
|
||||||
pkt2 = &core.Packet{
|
pkt2 = &core.Packet{
|
||||||
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
||||||
Payload: pkt.Payload,
|
Payload: pkt.Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecG711A:
|
case tutk.CodecPCMA:
|
||||||
name = core.CodecPCMA
|
name = core.CodecPCMA
|
||||||
pkt2 = &core.Packet{
|
pkt2 = &core.Packet{
|
||||||
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
||||||
Payload: pkt.Payload,
|
Payload: pkt.Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecAACADTS, tutk.AudioCodecAACWyze, tutk.AudioCodecAACRaw, tutk.AudioCodecAACLATM:
|
case tutk.CodecAACADTS, tutk.CodecAACAlt, tutk.CodecAACRaw, tutk.CodecAACLATM:
|
||||||
name = core.CodecAAC
|
name = core.CodecAAC
|
||||||
payload := pkt.Payload
|
payload := pkt.Payload
|
||||||
if aac.IsADTS(payload) {
|
if aac.IsADTS(payload) {
|
||||||
@@ -121,21 +121,21 @@ func (p *Producer) Start() error {
|
|||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecOpus:
|
case tutk.CodecOpus:
|
||||||
name = core.CodecOpus
|
name = core.CodecOpus
|
||||||
pkt2 = &core.Packet{
|
pkt2 = &core.Packet{
|
||||||
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
||||||
Payload: pkt.Payload,
|
Payload: pkt.Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecPCM:
|
case tutk.CodecPCML:
|
||||||
name = core.CodecPCML
|
name = core.CodecPCML
|
||||||
pkt2 = &core.Packet{
|
pkt2 = &core.Packet{
|
||||||
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
||||||
Payload: pkt.Payload,
|
Payload: pkt.Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.AudioCodecMP3:
|
case tutk.CodecMP3:
|
||||||
name = core.CodecMP3
|
name = core.CodecMP3
|
||||||
pkt2 = &core.Packet{
|
pkt2 = &core.Packet{
|
||||||
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
Header: rtp.Header{Version: 2, Marker: true, SequenceNumber: uint16(pkt.FrameNo), Timestamp: pkt.Timestamp},
|
||||||
@@ -167,7 +167,7 @@ func probe(client *Client, quality byte) ([]*core.Media, error) {
|
|||||||
client.SetDeadline(time.Now().Add(core.ProbeTimeout))
|
client.SetDeadline(time.Now().Add(core.ProbeTimeout))
|
||||||
|
|
||||||
var vcodec, acodec *core.Codec
|
var vcodec, acodec *core.Codec
|
||||||
var tutkAudioCodec uint16
|
var tutkAudioCodec byte
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if client.verbose {
|
if client.verbose {
|
||||||
@@ -197,33 +197,33 @@ func probe(client *Client, quality byte) ([]*core.Media, error) {
|
|||||||
vcodec = h265.AVCCToCodec(buf)
|
vcodec = h265.AVCCToCodec(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecG711U:
|
case tutk.CodecPCMU:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecPCMU, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
acodec = &core.Codec{Name: core.CodecPCMU, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecG711A:
|
case tutk.CodecPCMA:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecPCMA, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
acodec = &core.Codec{Name: core.CodecPCMA, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecAACWyze, tutk.AudioCodecAACADTS, tutk.AudioCodecAACRaw, tutk.AudioCodecAACLATM:
|
case tutk.CodecAACAlt, tutk.CodecAACADTS, tutk.CodecAACRaw, tutk.CodecAACLATM:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
config := aac.EncodeConfig(aac.TypeAACLC, pkt.SampleRate, pkt.Channels, false)
|
config := aac.EncodeConfig(aac.TypeAACLC, pkt.SampleRate, pkt.Channels, false)
|
||||||
acodec = aac.ConfigToCodec(config)
|
acodec = aac.ConfigToCodec(config)
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecOpus:
|
case tutk.CodecOpus:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecOpus, ClockRate: 48000, Channels: 2}
|
acodec = &core.Codec{Name: core.CodecOpus, ClockRate: 48000, Channels: 2}
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecPCM:
|
case tutk.CodecPCML:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecPCML, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
acodec = &core.Codec{Name: core.CodecPCML, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
}
|
}
|
||||||
case tutk.AudioCodecMP3:
|
case tutk.CodecMP3:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecMP3, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
acodec = &core.Codec{Name: core.CodecMP3, ClockRate: pkt.SampleRate, Channels: pkt.Channels}
|
||||||
tutkAudioCodec = pkt.Codec
|
tutkAudioCodec = pkt.Codec
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,281 +0,0 @@
|
|||||||
package tutk
|
|
||||||
|
|
||||||
type AVLoginResponse struct {
|
|
||||||
ServerType uint32
|
|
||||||
Resend int32
|
|
||||||
TwoWayStreaming int32
|
|
||||||
SyncRecvData int32
|
|
||||||
SecurityMode uint32
|
|
||||||
VideoOnConnect int32
|
|
||||||
AudioOnConnect int32
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
CodecUnknown uint16 = 0x00
|
|
||||||
CodecMPEG4 uint16 = 0x4C // 76
|
|
||||||
CodecH263 uint16 = 0x4D // 77
|
|
||||||
CodecH264 uint16 = 0x4E // 78
|
|
||||||
CodecMJPEG uint16 = 0x4F // 79
|
|
||||||
CodecH265 uint16 = 0x50 // 80
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
AudioCodecAACRaw uint16 = 0x86 // 134
|
|
||||||
AudioCodecAACADTS uint16 = 0x87 // 135
|
|
||||||
AudioCodecAACLATM uint16 = 0x88 // 136
|
|
||||||
AudioCodecG711U uint16 = 0x89 // 137
|
|
||||||
AudioCodecG711A uint16 = 0x8A // 138
|
|
||||||
AudioCodecADPCM uint16 = 0x8B // 139
|
|
||||||
AudioCodecPCM uint16 = 0x8C // 140
|
|
||||||
AudioCodecSPEEX uint16 = 0x8D // 141
|
|
||||||
AudioCodecMP3 uint16 = 0x8E // 142
|
|
||||||
AudioCodecG726 uint16 = 0x8F // 143
|
|
||||||
AudioCodecAACWyze uint16 = 0x90 // 144
|
|
||||||
AudioCodecOpus uint16 = 0x92 // 146
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SampleRate8K uint8 = 0x00
|
|
||||||
SampleRate11K uint8 = 0x01
|
|
||||||
SampleRate12K uint8 = 0x02
|
|
||||||
SampleRate16K uint8 = 0x03
|
|
||||||
SampleRate22K uint8 = 0x04
|
|
||||||
SampleRate24K uint8 = 0x05
|
|
||||||
SampleRate32K uint8 = 0x06
|
|
||||||
SampleRate44K uint8 = 0x07
|
|
||||||
SampleRate48K uint8 = 0x08
|
|
||||||
)
|
|
||||||
|
|
||||||
var sampleRates = map[uint8]int{
|
|
||||||
SampleRate8K: 8000,
|
|
||||||
SampleRate11K: 11025,
|
|
||||||
SampleRate12K: 12000,
|
|
||||||
SampleRate16K: 16000,
|
|
||||||
SampleRate22K: 22050,
|
|
||||||
SampleRate24K: 24000,
|
|
||||||
SampleRate32K: 32000,
|
|
||||||
SampleRate44K: 44100,
|
|
||||||
SampleRate48K: 48000,
|
|
||||||
}
|
|
||||||
|
|
||||||
var samplesPerFrame = map[uint16]uint32{
|
|
||||||
AudioCodecAACRaw: 1024,
|
|
||||||
AudioCodecAACADTS: 1024,
|
|
||||||
AudioCodecAACLATM: 1024,
|
|
||||||
AudioCodecAACWyze: 1024,
|
|
||||||
AudioCodecG711U: 160,
|
|
||||||
AudioCodecG711A: 160,
|
|
||||||
AudioCodecPCM: 160,
|
|
||||||
AudioCodecADPCM: 160,
|
|
||||||
AudioCodecSPEEX: 160,
|
|
||||||
AudioCodecMP3: 1152,
|
|
||||||
AudioCodecG726: 160,
|
|
||||||
AudioCodecOpus: 960,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
IOTypeVideoStart = 0x01FF
|
|
||||||
IOTypeVideoStop = 0x02FF
|
|
||||||
IOTypeAudioStart = 0x0300
|
|
||||||
IOTypeAudioStop = 0x0301
|
|
||||||
IOTypeSpeakerStart = 0x0350
|
|
||||||
IOTypeSpeakerStop = 0x0351
|
|
||||||
IOTypeGetAudioOutFormatReq = 0x032A
|
|
||||||
IOTypeGetAudioOutFormatRes = 0x032B
|
|
||||||
IOTypeSetStreamCtrlReq = 0x0320
|
|
||||||
IOTypeSetStreamCtrlRes = 0x0321
|
|
||||||
IOTypeGetStreamCtrlReq = 0x0322
|
|
||||||
IOTypeGetStreamCtrlRes = 0x0323
|
|
||||||
IOTypeDevInfoReq = 0x0340
|
|
||||||
IOTypeDevInfoRes = 0x0341
|
|
||||||
IOTypeGetSupportStreamReq = 0x0344
|
|
||||||
IOTypeGetSupportStreamRes = 0x0345
|
|
||||||
IOTypeSetRecordReq = 0x0310
|
|
||||||
IOTypeSetRecordRes = 0x0311
|
|
||||||
IOTypeGetRecordReq = 0x0312
|
|
||||||
IOTypeGetRecordRes = 0x0313
|
|
||||||
IOTypePTZCommand = 0x1001
|
|
||||||
IOTypeReceiveFirstFrame = 0x1002
|
|
||||||
IOTypeGetEnvironmentReq = 0x030A
|
|
||||||
IOTypeGetEnvironmentRes = 0x030B
|
|
||||||
IOTypeSetVideoModeReq = 0x030C
|
|
||||||
IOTypeSetVideoModeRes = 0x030D
|
|
||||||
IOTypeGetVideoModeReq = 0x030E
|
|
||||||
IOTypeGetVideoModeRes = 0x030F
|
|
||||||
IOTypeSetTimeReq = 0x0316
|
|
||||||
IOTypeSetTimeRes = 0x0317
|
|
||||||
IOTypeGetTimeReq = 0x0318
|
|
||||||
IOTypeGetTimeRes = 0x0319
|
|
||||||
IOTypeSetWifiReq = 0x0102
|
|
||||||
IOTypeSetWifiRes = 0x0103
|
|
||||||
IOTypeGetWifiReq = 0x0104
|
|
||||||
IOTypeGetWifiRes = 0x0105
|
|
||||||
IOTypeListWifiAPReq = 0x0106
|
|
||||||
IOTypeListWifiAPRes = 0x0107
|
|
||||||
IOTypeSetMotionDetectReq = 0x0306
|
|
||||||
IOTypeSetMotionDetectRes = 0x0307
|
|
||||||
IOTypeGetMotionDetectReq = 0x0308
|
|
||||||
IOTypeGetMotionDetectRes = 0x0309
|
|
||||||
)
|
|
||||||
|
|
||||||
// OLD Protocol (IOTC/TransCode)
|
|
||||||
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
|
|
||||||
|
|
||||||
OldHeaderSize = 16
|
|
||||||
OldDiscoBodySize = 72
|
|
||||||
OldDiscoSize = OldHeaderSize + OldDiscoBodySize
|
|
||||||
OldSessionBody = 36
|
|
||||||
OldSessionSize = OldHeaderSize + OldSessionBody
|
|
||||||
)
|
|
||||||
|
|
||||||
// NEW Protocol (0xCC51)
|
|
||||||
const (
|
|
||||||
MagicNewProto uint16 = 0xCC51
|
|
||||||
CmdNewDisco uint16 = 0x1002
|
|
||||||
CmdNewKeepalive uint16 = 0x1202
|
|
||||||
CmdNewClose uint16 = 0x1302
|
|
||||||
CmdNewDTLS uint16 = 0x1502
|
|
||||||
NewPayloadSize uint16 = 0x0028
|
|
||||||
NewPacketSize = 52
|
|
||||||
NewHeaderSize = 28
|
|
||||||
NewAuthSize = 20
|
|
||||||
NewKeepaliveSize = 48
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UIDSize = 20
|
|
||||||
RandIDSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PSKIdentity = "AUTHPWD_admin"
|
|
||||||
DefaultUser = "admin"
|
|
||||||
DefaultPort = 32761
|
|
||||||
)
|
|
||||||
|
|
||||||
func CodecName(id uint16) string {
|
|
||||||
switch id {
|
|
||||||
case CodecH264:
|
|
||||||
return "H264"
|
|
||||||
case CodecH265:
|
|
||||||
return "H265"
|
|
||||||
case CodecMPEG4:
|
|
||||||
return "MPEG4"
|
|
||||||
case CodecH263:
|
|
||||||
return "H263"
|
|
||||||
case CodecMJPEG:
|
|
||||||
return "MJPEG"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AudioCodecName(id uint16) string {
|
|
||||||
switch id {
|
|
||||||
case AudioCodecG711U:
|
|
||||||
return "PCMU"
|
|
||||||
case AudioCodecG711A:
|
|
||||||
return "PCMA"
|
|
||||||
case AudioCodecPCM:
|
|
||||||
return "PCM"
|
|
||||||
case AudioCodecAACLATM, AudioCodecAACRaw, AudioCodecAACADTS, AudioCodecAACWyze:
|
|
||||||
return "AAC"
|
|
||||||
case AudioCodecOpus:
|
|
||||||
return "Opus"
|
|
||||||
case AudioCodecSPEEX:
|
|
||||||
return "Speex"
|
|
||||||
case AudioCodecMP3:
|
|
||||||
return "MP3"
|
|
||||||
case AudioCodecG726:
|
|
||||||
return "G726"
|
|
||||||
case AudioCodecADPCM:
|
|
||||||
return "ADPCM"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SampleRateValue(idx uint8) int {
|
|
||||||
if rate, ok := sampleRates[idx]; ok {
|
|
||||||
return rate
|
|
||||||
}
|
|
||||||
return 16000
|
|
||||||
}
|
|
||||||
|
|
||||||
func SampleRateIndex(hz uint32) uint8 {
|
|
||||||
switch hz {
|
|
||||||
case 8000:
|
|
||||||
return SampleRate8K
|
|
||||||
case 11025:
|
|
||||||
return SampleRate11K
|
|
||||||
case 12000:
|
|
||||||
return SampleRate12K
|
|
||||||
case 16000:
|
|
||||||
return SampleRate16K
|
|
||||||
case 22050:
|
|
||||||
return SampleRate22K
|
|
||||||
case 24000:
|
|
||||||
return SampleRate24K
|
|
||||||
case 32000:
|
|
||||||
return SampleRate32K
|
|
||||||
case 44100:
|
|
||||||
return SampleRate44K
|
|
||||||
case 48000:
|
|
||||||
return SampleRate48K
|
|
||||||
default:
|
|
||||||
return SampleRate16K
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildAudioFlags(sampleRate uint32, bits16, stereo bool) uint8 {
|
|
||||||
flags := SampleRateIndex(sampleRate) << 2
|
|
||||||
if bits16 {
|
|
||||||
flags |= 0x02
|
|
||||||
}
|
|
||||||
if stereo {
|
|
||||||
flags |= 0x01
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsVideoCodec(id uint16) bool {
|
|
||||||
return id >= CodecMPEG4 && id <= CodecH265
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsAudioCodec(id uint16) bool {
|
|
||||||
return id >= AudioCodecAACRaw && id <= AudioCodecOpus
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSamplesPerFrame(codecID uint16) uint32 {
|
|
||||||
if samples, ok := samplesPerFrame[codecID]; ok {
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
return 1024
|
|
||||||
}
|
|
||||||
@@ -107,7 +107,7 @@ func (c *Client) ReadPacket() (hdr, payload []byte, err error) {
|
|||||||
switch hdr[0] {
|
switch hdr[0] {
|
||||||
case tutk.CodecH264, tutk.CodecH265:
|
case tutk.CodecH264, tutk.CodecH265:
|
||||||
payload, err = DecodeVideo(payload, c.key)
|
payload, err = DecodeVideo(payload, c.key)
|
||||||
case tutk.CodecAAC:
|
case tutk.CodecAACLATM:
|
||||||
payload, err = crypto.Decode(payload, c.key)
|
payload, err = crypto.Decode(payload, c.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func probe(client *Client) ([]*core.Media, error) {
|
|||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = &core.Codec{Name: core.CodecPCML, ClockRate: 8000}
|
acodec = &core.Codec{Name: core.CodecPCML, ClockRate: 8000}
|
||||||
}
|
}
|
||||||
case tutk.CodecAAC:
|
case tutk.CodecAACLATM:
|
||||||
if acodec == nil {
|
if acodec == nil {
|
||||||
acodec = aac.ADTSToCodec(payload)
|
acodec = aac.ADTSToCodec(payload)
|
||||||
if acodec != nil {
|
if acodec != nil {
|
||||||
@@ -187,7 +187,7 @@ func (c *Producer) Start() error {
|
|||||||
audioTS += uint32(n / 2) // because 16bit
|
audioTS += uint32(n / 2) // because 16bit
|
||||||
}
|
}
|
||||||
|
|
||||||
case tutk.CodecAAC:
|
case tutk.CodecAACLATM:
|
||||||
pkt = &core.Packet{
|
pkt = &core.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
SequenceNumber: audioSeq,
|
SequenceNumber: audioSeq,
|
||||||
|
|||||||
Reference in New Issue
Block a user