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>
239 lines
5.6 KiB
Go
239 lines
5.6 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
muxReadBufSize = 8192
|
||
muxChanBufSize = 256
|
||
)
|
||
|
||
// isDTLS returns true if the first byte indicates a DTLS record (RFC 7983: 20–63).
|
||
func isDTLS(b byte) bool {
|
||
return b >= 20 && b <= 63
|
||
}
|
||
|
||
// isRTPOrRTCP returns true if the first byte indicates an RTP/RTCP packet (RFC 7983: 128–191).
|
||
func isRTPOrRTCP(b byte) bool {
|
||
return b >= 128 && b <= 191
|
||
}
|
||
|
||
// isRTCP returns true if the packet is RTCP (not RTP) per RFC 5761 Section 4.
|
||
// RTCP packet types (byte[1]) are 200-211. RTP with Marker=1 and dynamic PT >= 96
|
||
// gives byte[1] >= 224, so we use byte[1] >= 200 && byte[1] < 224 to exclude RTP.
|
||
// In SRTCP the fixed header is unencrypted, so byte[1] is readable.
|
||
func isRTCP(pkt []byte) bool {
|
||
return len(pkt) >= 2 && pkt[1] >= 200 && pkt[1] < 224
|
||
}
|
||
|
||
// PacketDemux reads from a net.Conn and routes packets to separate DTLS,
|
||
// SRTP (RTP only), and RTCP channels based on RFC 7983 first-byte classification
|
||
// and RTP/RTCP payload type demux.
|
||
type PacketDemux struct {
|
||
conn net.Conn
|
||
dtlsCh chan []byte
|
||
srtpCh chan []byte
|
||
rtcpCh chan []byte
|
||
once sync.Once
|
||
closed chan struct{}
|
||
label string
|
||
}
|
||
|
||
func (d *PacketDemux) logf(format string, args ...interface{}) {
|
||
fmt.Printf("[demux-%s] %s\n", d.label, fmt.Sprintf(format, args...))
|
||
}
|
||
|
||
// NewPacketDemux creates a PacketDemux and starts the read loop goroutine.
|
||
func NewPacketDemux(conn net.Conn, label string) *PacketDemux {
|
||
d := &PacketDemux{
|
||
conn: conn,
|
||
dtlsCh: make(chan []byte, muxChanBufSize),
|
||
srtpCh: make(chan []byte, muxChanBufSize),
|
||
rtcpCh: make(chan []byte, muxChanBufSize),
|
||
closed: make(chan struct{}),
|
||
label: label,
|
||
}
|
||
go d.readLoop()
|
||
return d
|
||
}
|
||
|
||
func (d *PacketDemux) readLoop() {
|
||
buf := make([]byte, muxReadBufSize)
|
||
dtlsCount := 0
|
||
srtpCount := 0
|
||
rtcpCount := 0
|
||
otherCount := 0
|
||
for {
|
||
n, err := d.conn.Read(buf)
|
||
if err != nil {
|
||
d.Close()
|
||
return
|
||
}
|
||
if n == 0 {
|
||
continue
|
||
}
|
||
pkt := make([]byte, n)
|
||
copy(pkt, buf[:n])
|
||
|
||
switch {
|
||
case isDTLS(pkt[0]):
|
||
dtlsCount++
|
||
if dtlsCount <= 5 {
|
||
d.logf("DTLS packet #%d: %d bytes (first byte: 0x%02x)", dtlsCount, n, pkt[0])
|
||
}
|
||
select {
|
||
case d.dtlsCh <- pkt:
|
||
default:
|
||
d.logf("DTLS channel full, dropping packet")
|
||
}
|
||
case isRTPOrRTCP(pkt[0]):
|
||
if isRTCP(pkt) {
|
||
rtcpCount++
|
||
if rtcpCount <= 3 {
|
||
d.logf("RTCP packet #%d: %d bytes (type byte: 0x%02x)", rtcpCount, n, pkt[1])
|
||
}
|
||
select {
|
||
case d.rtcpCh <- pkt:
|
||
default:
|
||
// drop if channel full
|
||
}
|
||
} else {
|
||
srtpCount++
|
||
if srtpCount == 1 {
|
||
d.logf("First SRTP packet: %d bytes", n)
|
||
}
|
||
select {
|
||
case d.srtpCh <- pkt:
|
||
default:
|
||
// drop if channel full
|
||
}
|
||
}
|
||
default:
|
||
otherCount++
|
||
if otherCount <= 3 {
|
||
d.logf("Other packet: %d bytes (first byte: 0x%02x)", n, pkt[0])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Close shuts down the demuxer and the underlying connection.
|
||
func (d *PacketDemux) Close() error {
|
||
var err error
|
||
d.once.Do(func() {
|
||
close(d.closed)
|
||
err = d.conn.Close()
|
||
})
|
||
return err
|
||
}
|
||
|
||
// DTLSEndpoint returns a net.Conn that yields only DTLS packets.
|
||
func (d *PacketDemux) DTLSEndpoint() net.Conn {
|
||
return &demuxEndpoint{demux: d, ch: d.dtlsCh}
|
||
}
|
||
|
||
// SRTPEndpoint returns a net.Conn that yields only SRTP (RTP) packets.
|
||
// RTCP packets are routed to RTCPChannel() instead.
|
||
func (d *PacketDemux) SRTPEndpoint() net.Conn {
|
||
return &demuxEndpoint{demux: d, ch: d.srtpCh}
|
||
}
|
||
|
||
// RTCPChannel returns a channel that receives raw encrypted SRTCP packets.
|
||
// These must be decrypted externally (not via SessionSRTP which only handles RTP).
|
||
func (d *PacketDemux) RTCPChannel() <-chan []byte {
|
||
return d.rtcpCh
|
||
}
|
||
|
||
// demuxEndpoint implements net.Conn for a single demux channel.
|
||
type demuxEndpoint struct {
|
||
demux *PacketDemux
|
||
ch chan []byte
|
||
mu sync.Mutex
|
||
leftover []byte
|
||
}
|
||
|
||
func (e *demuxEndpoint) Read(b []byte) (int, error) {
|
||
e.mu.Lock()
|
||
if len(e.leftover) > 0 {
|
||
n := copy(b, e.leftover)
|
||
e.leftover = e.leftover[n:]
|
||
if len(e.leftover) == 0 {
|
||
e.leftover = nil
|
||
}
|
||
e.mu.Unlock()
|
||
return n, nil
|
||
}
|
||
e.mu.Unlock()
|
||
|
||
select {
|
||
case <-e.demux.closed:
|
||
return 0, io.EOF
|
||
case pkt, ok := <-e.ch:
|
||
if !ok {
|
||
return 0, io.EOF
|
||
}
|
||
n := copy(b, pkt)
|
||
if n < len(pkt) {
|
||
e.mu.Lock()
|
||
e.leftover = pkt[n:]
|
||
e.mu.Unlock()
|
||
}
|
||
return n, nil
|
||
}
|
||
}
|
||
|
||
func (e *demuxEndpoint) Write(b []byte) (int, error) {
|
||
return e.demux.conn.Write(b)
|
||
}
|
||
|
||
func (e *demuxEndpoint) Close() error {
|
||
return e.demux.Close()
|
||
}
|
||
|
||
func (e *demuxEndpoint) LocalAddr() net.Addr {
|
||
return e.demux.conn.LocalAddr()
|
||
}
|
||
|
||
func (e *demuxEndpoint) RemoteAddr() net.Addr {
|
||
return e.demux.conn.RemoteAddr()
|
||
}
|
||
|
||
func (e *demuxEndpoint) SetDeadline(t time.Time) error {
|
||
return e.demux.conn.SetDeadline(t)
|
||
}
|
||
|
||
func (e *demuxEndpoint) SetReadDeadline(t time.Time) error {
|
||
return e.demux.conn.SetReadDeadline(t)
|
||
}
|
||
|
||
func (e *demuxEndpoint) SetWriteDeadline(t time.Time) error {
|
||
return e.demux.conn.SetWriteDeadline(t)
|
||
}
|
||
|
||
// connToPacketConn wraps a net.Conn into a net.PacketConn.
|
||
// It is used to adapt a demuxEndpoint for pion/dtls.Server(), which
|
||
// requires net.PacketConn. Since the endpoint is already bound to a
|
||
// single peer, ReadFrom returns the conn's RemoteAddr and WriteTo ignores
|
||
// the addr parameter.
|
||
type connToPacketConn struct {
|
||
net.Conn
|
||
}
|
||
|
||
// WrapAsPacketConn adapts a net.Conn to net.PacketConn.
|
||
func WrapAsPacketConn(c net.Conn) net.PacketConn {
|
||
return &connToPacketConn{Conn: c}
|
||
}
|
||
|
||
func (c *connToPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||
n, err := c.Conn.Read(b)
|
||
return n, c.Conn.RemoteAddr(), err
|
||
}
|
||
|
||
func (c *connToPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
|
||
return c.Conn.Write(b)
|
||
}
|