mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Squashed buildout of the tgcalls testbench: - CLI test tool with --mode p2p/reflector/group/group-churn, cross-version interop (--version, --version2), and quiet/summary output - Linux toolchain + Docker multi-stage build, AWS Fargate mass test harness, local parallel mass test harness with signaling loss simulation - SCTP writable gate, retransmission timer tuning, role-based handshake - InstanceV2CompatImpl (PeerConnection backend with V2Impl signaling) and SignalingTranslator for v14.0.0 interop - In-process Go/Pion SFU (ICE+DTLS+SRTP+SCTP per participant) with audio RTP forwarding, ActiveAudio/VideoSsrcs data channel broadcast, RTCP feedback path, and CGo c-archive integration - GroupInstanceReferenceImpl (PeerConnection group-call) and mixed-impl group mode (--reference-participants), with SDP munging for simulcast - H264 simulcast group video (FakeVideoTrackSource pattern generator, FakeVideoSink frame counting, --video flag, two-pass channel setup, reactive video setup from ActiveVideoSsrcs) - Group churn stress mode (--mode group-churn, --churn-cycles) - SFU stream-quality adaptation: BandwidthEstimator, LayerSelector state machine, RtxRingBuffer, simulcast SSRC rewrite - Transport-cc feedback generation, NetworkSimulator (delay/jitter/loss/ token-bucket bandwidth), --network-scenario step-down-up - CLAUDE.md updates throughout Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pion/rtcp"
|
|
)
|
|
|
|
// --- RTP Header Extension Parsing ---
|
|
|
|
// parseTWCCSeq extracts the transport-wide sequence number from an RTP packet.
|
|
// extID is the header extension ID to look for (typically 3).
|
|
// Returns the sequence number and true if found, or 0 and false.
|
|
func parseTWCCSeq(pkt []byte, extID int) (uint16, bool) {
|
|
if len(pkt) < 12 {
|
|
return 0, false
|
|
}
|
|
|
|
// Check extension bit (X) in RTP header byte 0.
|
|
if pkt[0]&0x10 == 0 {
|
|
return 0, false
|
|
}
|
|
|
|
// Skip fixed header (12 bytes) + CSRC list.
|
|
cc := int(pkt[0] & 0x0F)
|
|
offset := 12 + cc*4
|
|
if offset+4 > len(pkt) {
|
|
return 0, false
|
|
}
|
|
|
|
// Check for one-byte header extension (0xBEDE magic).
|
|
if pkt[offset] != 0xBE || pkt[offset+1] != 0xDE {
|
|
return 0, false
|
|
}
|
|
|
|
// Extension length in 32-bit words.
|
|
extLen := int(binary.BigEndian.Uint16(pkt[offset+2:])) * 4
|
|
offset += 4
|
|
extEnd := offset + extLen
|
|
if extEnd > len(pkt) {
|
|
return 0, false
|
|
}
|
|
|
|
// Scan extension elements: [id:4][len:4][data...].
|
|
for offset < extEnd {
|
|
b := pkt[offset]
|
|
if b == 0 {
|
|
// Padding byte.
|
|
offset++
|
|
continue
|
|
}
|
|
id := int(b >> 4)
|
|
dataLen := int(b&0x0F) + 1 // len field is 0-based
|
|
offset++
|
|
if id == extID && dataLen >= 2 && offset+2 <= extEnd {
|
|
seq := binary.BigEndian.Uint16(pkt[offset:])
|
|
return seq, true
|
|
}
|
|
offset += dataLen
|
|
}
|
|
|
|
return 0, false
|
|
}
|
|
|
|
// --- Transport-CC Feedback Generator ---
|
|
|
|
type twccArrival struct {
|
|
seq uint16
|
|
arrivalUs int64 // microseconds since generator creation
|
|
}
|
|
|
|
// TransportCCGenerator generates RTCP transport-cc feedback for a sender.
|
|
// It tracks packet arrivals and emits feedback every 100ms.
|
|
type TransportCCGenerator struct {
|
|
mu sync.Mutex
|
|
arrivals []twccArrival
|
|
startTime time.Time
|
|
fbCount uint8 // feedback packet counter
|
|
|
|
// Callback to send the feedback RTCP packet.
|
|
sendFeedback func(data []byte)
|
|
|
|
stopCh chan struct{}
|
|
done chan struct{}
|
|
}
|
|
|
|
// NewTransportCCGenerator creates and starts a generator.
|
|
// sendFeedback is called with marshalled+encrypted RTCP data to send to the sender.
|
|
func NewTransportCCGenerator(sendFeedback func(data []byte)) *TransportCCGenerator {
|
|
g := &TransportCCGenerator{
|
|
startTime: time.Now(),
|
|
sendFeedback: sendFeedback,
|
|
stopCh: make(chan struct{}),
|
|
done: make(chan struct{}),
|
|
}
|
|
go g.run()
|
|
return g
|
|
}
|
|
|
|
// RecordArrival records a packet arrival. Thread-safe.
|
|
func (g *TransportCCGenerator) RecordArrival(twccSeq uint16) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
arrivalUs := time.Since(g.startTime).Microseconds()
|
|
g.arrivals = append(g.arrivals, twccArrival{seq: twccSeq, arrivalUs: arrivalUs})
|
|
}
|
|
|
|
// Stop terminates the generator.
|
|
func (g *TransportCCGenerator) Stop() {
|
|
close(g.stopCh)
|
|
<-g.done
|
|
}
|
|
|
|
func (g *TransportCCGenerator) run() {
|
|
defer close(g.done)
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-g.stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
g.emitFeedback()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *TransportCCGenerator) emitFeedback() {
|
|
g.mu.Lock()
|
|
if len(g.arrivals) == 0 {
|
|
g.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
// Take all arrivals.
|
|
arrivals := g.arrivals
|
|
g.arrivals = nil
|
|
g.fbCount++
|
|
fbCount := g.fbCount
|
|
g.mu.Unlock()
|
|
|
|
// Sort by sequence number (should already be mostly sorted).
|
|
for i := 1; i < len(arrivals); i++ {
|
|
for j := i; j > 0 && seqBefore(arrivals[j].seq, arrivals[j-1].seq); j-- {
|
|
arrivals[j], arrivals[j-1] = arrivals[j-1], arrivals[j]
|
|
}
|
|
}
|
|
|
|
baseSeq := arrivals[0].seq
|
|
// Number of sequence numbers covered (including gaps).
|
|
lastSeq := arrivals[len(arrivals)-1].seq
|
|
packetCount := seqDiff(baseSeq, lastSeq) + 1
|
|
|
|
// Reference time: arrival of first packet in 64ms units.
|
|
refTimeUs := arrivals[0].arrivalUs
|
|
refTime := uint32(refTimeUs / 64000) // 64ms units, 24-bit in spec but stored as uint32
|
|
|
|
// Build received set for gap detection.
|
|
receivedAt := make(map[uint16]int64, len(arrivals))
|
|
for _, a := range arrivals {
|
|
receivedAt[a.seq] = a.arrivalUs
|
|
}
|
|
|
|
// Build packet chunks and recv deltas.
|
|
var chunks []rtcp.PacketStatusChunk
|
|
var deltas []*rtcp.RecvDelta
|
|
|
|
// Process in runs of up to 7 (status vector chunk capacity for 2-bit symbols).
|
|
prevArrivalUs := refTimeUs
|
|
var statusList []uint16
|
|
|
|
seq := baseSeq
|
|
for i := 0; i < int(packetCount); i++ {
|
|
arrUs, received := receivedAt[seq]
|
|
if received {
|
|
deltaUs := arrUs - prevArrivalUs
|
|
if deltaUs >= 0 && deltaUs <= 63750 { // fits in small delta (0-255 * 250us)
|
|
statusList = append(statusList, rtcp.TypeTCCPacketReceivedSmallDelta)
|
|
deltas = append(deltas, &rtcp.RecvDelta{
|
|
Type: rtcp.TypeTCCPacketReceivedSmallDelta,
|
|
Delta: deltaUs,
|
|
})
|
|
} else {
|
|
statusList = append(statusList, rtcp.TypeTCCPacketReceivedLargeDelta)
|
|
deltas = append(deltas, &rtcp.RecvDelta{
|
|
Type: rtcp.TypeTCCPacketReceivedLargeDelta,
|
|
Delta: deltaUs,
|
|
})
|
|
}
|
|
prevArrivalUs = arrUs
|
|
} else {
|
|
statusList = append(statusList, rtcp.TypeTCCPacketNotReceived)
|
|
}
|
|
seq++
|
|
}
|
|
|
|
// Encode status list as status vector chunks (7 symbols per chunk with 2-bit symbols).
|
|
for i := 0; i < len(statusList); i += 7 {
|
|
end := i + 7
|
|
if end > len(statusList) {
|
|
end = len(statusList)
|
|
}
|
|
chunk := statusList[i:end]
|
|
|
|
// Check if all same status (use run-length).
|
|
allSame := true
|
|
for _, s := range chunk {
|
|
if s != chunk[0] {
|
|
allSame = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if allSame && len(chunk) >= 2 {
|
|
chunks = append(chunks, &rtcp.RunLengthChunk{
|
|
Type: rtcp.TypeTCCRunLengthChunk,
|
|
PacketStatusSymbol: chunk[0],
|
|
RunLength: uint16(len(chunk)),
|
|
})
|
|
} else {
|
|
// Status vector with 2-bit symbols.
|
|
symbolList := make([]uint16, len(chunk))
|
|
copy(symbolList, chunk)
|
|
chunks = append(chunks, &rtcp.StatusVectorChunk{
|
|
Type: rtcp.TypeTCCStatusVectorChunk,
|
|
SymbolSize: rtcp.TypeTCCSymbolSizeTwoBit,
|
|
SymbolList: symbolList,
|
|
})
|
|
}
|
|
}
|
|
|
|
fb := &rtcp.TransportLayerCC{
|
|
SenderSSRC: 1,
|
|
MediaSSRC: 0,
|
|
BaseSequenceNumber: baseSeq,
|
|
PacketStatusCount: packetCount,
|
|
ReferenceTime: refTime,
|
|
FbPktCount: fbCount,
|
|
PacketChunks: chunks,
|
|
RecvDeltas: deltas,
|
|
}
|
|
|
|
data, err := rtcp.Marshal([]rtcp.Packet{fb})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
g.sendFeedback(data)
|
|
}
|
|
|
|
// seqBefore returns true if a comes before b in the uint16 sequence space.
|
|
func seqBefore(a, b uint16) bool {
|
|
return int16(a-b) < 0
|
|
}
|
|
|
|
// seqDiff returns the forward distance from a to b in uint16 sequence space.
|
|
func seqDiff(a, b uint16) uint16 {
|
|
return b - a
|
|
}
|