use std::collections::HashMap;
use std::future::Future;
use tokio::time::{Duration, Instant, Interval, MissedTickBehavior};
use crate::error::Error;
use super::crypto::VoiceBox;
use super::rtp::{OPUS_PAYLOAD_TYPE, RtpPacket, build_rtp_header, is_rtcp_packet};
pub const SAMPLES_PER_FRAME: u32 = 960;
pub const FRAME_DURATION: Duration = Duration::from_millis(20);
pub const SILENCE_FRAME: [u8; 3] = [0xF8, 0xFF, 0xFE];
#[derive(Debug)]
pub struct RtpSession {
ssrc: u32,
payload_type: u8,
sequence: u16,
timestamp: u32,
nonce: u32,
}
impl RtpSession {
pub fn new(ssrc: u32) -> Self {
Self {
ssrc,
payload_type: OPUS_PAYLOAD_TYPE,
sequence: 0,
timestamp: 0,
nonce: 0,
}
}
pub const fn ssrc(&self) -> u32 {
self.ssrc
}
pub const fn sequence(&self) -> u16 {
self.sequence
}
pub const fn timestamp(&self) -> u32 {
self.timestamp
}
pub const fn nonce(&self) -> u32 {
self.nonce
}
pub fn current_header(&self) -> [u8; 12] {
build_rtp_header(self.payload_type, self.sequence, self.timestamp, self.ssrc)
}
pub fn seal_frame(&mut self, voice_box: &dyn VoiceBox, opus: &[u8]) -> Vec<u8> {
let header = self.current_header();
let sealed = voice_box.seal(&header, opus, self.nonce);
let mut packet = Vec::with_capacity(header.len() + sealed.len());
packet.extend_from_slice(&header);
packet.extend_from_slice(&sealed);
self.advance(SAMPLES_PER_FRAME);
packet
}
fn advance(&mut self, timestamp_step: u32) {
self.sequence = self.sequence.wrapping_add(1);
self.timestamp = self.timestamp.wrapping_add(timestamp_step);
self.nonce = self.nonce.wrapping_add(1);
}
}
#[derive(Debug)]
pub struct FramePacer {
interval: Interval,
}
impl FramePacer {
pub fn new() -> Self {
Self::with_period(FRAME_DURATION)
}
pub fn with_period(period: Duration) -> Self {
let mut interval = tokio::time::interval(period.max(Duration::from_millis(1)));
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
Self { interval }
}
pub async fn tick(&mut self) -> Instant {
self.interval.tick().await
}
}
impl Default for FramePacer {
fn default() -> Self {
Self::new()
}
}
pub trait AudioSource: Send {
fn next_frame(&mut self) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + Send;
}
#[derive(Debug, Clone)]
pub struct SilenceSource {
remaining: usize,
}
impl SilenceSource {
pub fn new() -> Self {
Self { remaining: 5 }
}
pub fn with_frames(frames: usize) -> Self {
Self { remaining: frames }
}
}
impl Default for SilenceSource {
fn default() -> Self {
Self::new()
}
}
impl AudioSource for SilenceSource {
async fn next_frame(&mut self) -> Result<Option<Vec<u8>>, Error> {
if self.remaining == 0 {
return Ok(None);
}
self.remaining -= 1;
Ok(Some(SILENCE_FRAME.to_vec()))
}
}
pub struct OpusFrameSource<I> {
frames: I,
}
impl<I> OpusFrameSource<I>
where
I: Iterator<Item = Vec<u8>> + Send,
{
pub fn new(frames: I) -> Self {
Self { frames }
}
}
impl<I> AudioSource for OpusFrameSource<I>
where
I: Iterator<Item = Vec<u8>> + Send,
{
async fn next_frame(&mut self) -> Result<Option<Vec<u8>>, Error> {
Ok(self.frames.next())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReceivedFrame {
pub user_id: Option<u64>,
pub ssrc: u32,
pub sequence: u16,
pub timestamp: u32,
pub opus: Vec<u8>,
}
pub struct VoiceReceiver {
voice_box: Box<dyn VoiceBox>,
ssrc_to_user: HashMap<u32, u64>,
}
impl VoiceReceiver {
pub fn new(voice_box: Box<dyn VoiceBox>) -> Self {
Self {
voice_box,
ssrc_to_user: HashMap::new(),
}
}
pub fn map_ssrc(&mut self, ssrc: u32, user_id: u64) {
self.ssrc_to_user.insert(ssrc, user_id);
}
pub fn forget_user(&mut self, user_id: u64) {
self.ssrc_to_user.retain(|_, mapped| *mapped != user_id);
}
pub fn ssrc_to_user(&self, ssrc: u32) -> Option<u64> {
self.ssrc_to_user.get(&ssrc).copied()
}
pub fn process(&self, datagram: &[u8]) -> Result<Option<ReceivedFrame>, Error> {
if is_rtcp_packet(datagram) {
return Ok(None);
}
let (packet, opus): (RtpPacket, Vec<u8>) = self.voice_box.open_packet(datagram)?;
Ok(Some(ReceivedFrame {
user_id: self.ssrc_to_user(packet.ssrc),
ssrc: packet.ssrc,
sequence: packet.sequence,
timestamp: packet.timestamp,
opus,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::voice::crypto::{MODE_AES256_GCM, MODE_XCHACHA20_POLY1305, create_voice_box};
fn key() -> Vec<u8> {
(0u8..32).collect()
}
#[test]
fn rtp_session_seals_and_advances_counters() {
let voice_box = create_voice_box(MODE_AES256_GCM, &key()).unwrap();
let mut session = RtpSession::new(321);
let packet1 = session.seal_frame(voice_box.as_ref(), b"frame-1");
assert_eq!(session.sequence(), 1);
assert_eq!(session.timestamp(), SAMPLES_PER_FRAME);
assert_eq!(session.nonce(), 1);
let packet2 = session.seal_frame(voice_box.as_ref(), b"frame-2");
let (parsed1, payload1) = voice_box.open_packet(&packet1).unwrap();
let (parsed2, payload2) = voice_box.open_packet(&packet2).unwrap();
assert_eq!(payload1, b"frame-1");
assert_eq!(payload2, b"frame-2");
assert_eq!(parsed1.ssrc, 321);
assert_eq!(parsed2.sequence, parsed1.sequence + 1);
assert_eq!(parsed2.timestamp, parsed1.timestamp + SAMPLES_PER_FRAME);
}
#[test]
fn rtp_session_counters_wrap() {
let voice_box = create_voice_box(MODE_AES256_GCM, &key()).unwrap();
let mut session = RtpSession::new(1);
session.sequence = u16::MAX;
session.timestamp = u32::MAX - 100;
session.nonce = u32::MAX;
let _ = session.seal_frame(voice_box.as_ref(), b"x");
assert_eq!(session.sequence(), 0);
assert_eq!(session.nonce(), 0);
assert_eq!(session.timestamp(), SAMPLES_PER_FRAME - 101);
}
#[test]
fn receiver_decrypts_and_demuxes_by_user() {
let sender_box = create_voice_box(MODE_XCHACHA20_POLY1305, &key()).unwrap();
let mut session_a = RtpSession::new(100);
let mut session_b = RtpSession::new(200);
let packet_a = session_a.seal_frame(sender_box.as_ref(), b"from-a");
let packet_b = session_b.seal_frame(sender_box.as_ref(), b"from-b");
let mut rtcp = vec![0x80, 201];
rtcp.extend_from_slice(&[0u8; 10]);
let mut receiver =
VoiceReceiver::new(create_voice_box(MODE_XCHACHA20_POLY1305, &key()).unwrap());
receiver.map_ssrc(100, 111);
receiver.map_ssrc(200, 222);
let frame_a = receiver.process(&packet_a).unwrap().unwrap();
assert_eq!(frame_a.user_id, Some(111));
assert_eq!(frame_a.opus, b"from-a");
let frame_b = receiver.process(&packet_b).unwrap().unwrap();
assert_eq!(frame_b.user_id, Some(222));
assert_eq!(frame_b.opus, b"from-b");
assert_eq!(receiver.process(&rtcp).unwrap(), None);
receiver.forget_user(111);
let unmapped = receiver.process(&{
let mut session = RtpSession::new(100);
session.seal_frame(sender_box.as_ref(), b"late")
});
assert_eq!(unmapped.unwrap().unwrap().user_id, None);
}
#[test]
fn receiver_rejects_undecryptable_packets() {
let receiver = VoiceReceiver::new(create_voice_box(MODE_AES256_GCM, &key()).unwrap());
let mut bogus = crate::voice::rtp::make_test_rtp_header(1, 960, 7, false);
bogus.extend_from_slice(&[0u8; 24]);
assert!(receiver.process(&bogus).is_err());
}
#[tokio::test]
async fn silence_source_yields_fixed_frames() {
let mut source = SilenceSource::with_frames(3);
let mut frames = 0;
while let Some(frame) = source.next_frame().await.unwrap() {
assert_eq!(frame, SILENCE_FRAME.to_vec());
frames += 1;
}
assert_eq!(frames, 3);
}
#[tokio::test]
async fn opus_frame_source_wraps_iterators() {
let mut source = OpusFrameSource::new(vec![vec![1u8], vec![2u8]].into_iter());
assert_eq!(source.next_frame().await.unwrap(), Some(vec![1]));
assert_eq!(source.next_frame().await.unwrap(), Some(vec![2]));
assert_eq!(source.next_frame().await.unwrap(), None);
}
#[tokio::test]
async fn frame_pacer_delays_after_missed_ticks_instead_of_bursting() {
tokio::time::pause();
let mut pacer = FramePacer::new();
pacer.tick().await; tokio::time::advance(Duration::from_millis(50)).await;
let late = pacer.tick().await;
let next = pacer.tick().await;
assert!(next.duration_since(late) >= FRAME_DURATION);
}
}