Skip to main content

rtp_engine/
srtp.rs

1//! SRTP (Secure RTP) implementation per RFC 3711.
2//!
3//! Provides AES-128-CM encryption with HMAC-SHA1-80 authentication for
4//! securing RTP and RTCP media streams using SDES key exchange (RFC 4568).
5//!
6//! # Example
7//!
8//! ```
9//! use rtp_engine::srtp::SrtpContext;
10//!
11//! // Generate keying material
12//! let (mut sender, key_material) = SrtpContext::generate().unwrap();
13//! let mut receiver = SrtpContext::from_base64(&key_material).unwrap();
14//!
15//! // Encrypt RTP packet
16//! let rtp = vec![0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xA0,
17//!                0x12, 0x34, 0x56, 0x78, 0xDE, 0xAD, 0xBE, 0xEF];
18//! let srtp = sender.protect_rtp(&rtp).unwrap();
19//!
20//! // Decrypt on receiver side
21//! let decrypted = receiver.unprotect_rtp(&srtp).unwrap();
22//! assert_eq!(decrypted, rtp);
23//! ```
24
25use aes::cipher::{KeyIvInit, StreamCipher};
26use hmac::{Hmac, Mac};
27use sha1::Sha1;
28
29use crate::error::{Error, Result};
30
31type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
32type HmacSha1 = Hmac<Sha1>;
33
34const AUTH_TAG_LEN: usize = 10;
35const MASTER_KEY_LEN: usize = 16;
36const MASTER_SALT_LEN: usize = 14;
37
38/// Total keying material length (master key + master salt).
39pub const KEYING_MATERIAL_LEN: usize = MASTER_KEY_LEN + MASTER_SALT_LEN;
40
41const SESSION_KEY_LEN: usize = 16;
42const SESSION_SALT_LEN: usize = 14;
43const SESSION_AUTH_KEY_LEN: usize = 20;
44
45const LABEL_RTP_CIPHER: u8 = 0x00;
46const LABEL_RTP_AUTH: u8 = 0x01;
47const LABEL_RTP_SALT: u8 = 0x02;
48const LABEL_RTCP_CIPHER: u8 = 0x03;
49const LABEL_RTCP_AUTH: u8 = 0x04;
50const LABEL_RTCP_SALT: u8 = 0x05;
51
52/// SRTP cryptographic context for a single media session.
53///
54/// Maintains session keys and rollover counter for sequence number tracking.
55pub struct SrtpContext {
56    session_key: [u8; SESSION_KEY_LEN],
57    session_salt: [u8; SESSION_SALT_LEN],
58    session_auth_key: [u8; SESSION_AUTH_KEY_LEN],
59    rtcp_session_key: [u8; SESSION_KEY_LEN],
60    rtcp_session_salt: [u8; SESSION_SALT_LEN],
61    rtcp_session_auth_key: [u8; SESSION_AUTH_KEY_LEN],
62    roc: u32,
63    s_l: u16,
64    initialized: bool,
65    srtcp_index: u32,
66}
67
68impl SrtpContext {
69    /// Create a new SRTP context from master key and master salt.
70    ///
71    /// The master key must be 16 bytes and master salt must be 14 bytes
72    /// (AES_CM_128_HMAC_SHA1_80 profile).
73    pub fn new(master_key: &[u8], master_salt: &[u8]) -> Result<Self> {
74        if master_key.len() != MASTER_KEY_LEN {
75            return Err(Error::srtp(format!(
76                "master key must be {} bytes, got {}",
77                MASTER_KEY_LEN,
78                master_key.len()
79            )));
80        }
81        if master_salt.len() != MASTER_SALT_LEN {
82            return Err(Error::srtp(format!(
83                "master salt must be {} bytes, got {}",
84                MASTER_SALT_LEN,
85                master_salt.len()
86            )));
87        }
88
89        let session_key = derive_session_key(master_key, master_salt, LABEL_RTP_CIPHER)?;
90        let session_salt = derive_session_salt(master_key, master_salt, LABEL_RTP_SALT)?;
91        let session_auth_key = derive_session_auth_key(master_key, master_salt, LABEL_RTP_AUTH)?;
92
93        let rtcp_session_key = derive_session_key(master_key, master_salt, LABEL_RTCP_CIPHER)?;
94        let rtcp_session_salt = derive_session_salt(master_key, master_salt, LABEL_RTCP_SALT)?;
95        let rtcp_session_auth_key =
96            derive_session_auth_key(master_key, master_salt, LABEL_RTCP_AUTH)?;
97
98        Ok(Self {
99            session_key,
100            session_salt,
101            session_auth_key,
102            rtcp_session_key,
103            rtcp_session_salt,
104            rtcp_session_auth_key,
105            roc: 0,
106            s_l: 0,
107            initialized: false,
108            srtcp_index: 0,
109        })
110    }
111
112    /// Create from base64-encoded keying material (as used in SDP `a=crypto`).
113    pub fn from_base64(b64_key_material: &str) -> Result<Self> {
114        use base64::Engine;
115        let decoded = base64::engine::general_purpose::STANDARD
116            .decode(b64_key_material.trim())
117            .map_err(|e| Error::srtp(format!("base64 decode error: {}", e)))?;
118        if decoded.len() < KEYING_MATERIAL_LEN {
119            return Err(Error::srtp(format!(
120                "keying material too short: {} bytes, need {}",
121                decoded.len(),
122                KEYING_MATERIAL_LEN
123            )));
124        }
125        Self::new(
126            &decoded[..MASTER_KEY_LEN],
127            &decoded[MASTER_KEY_LEN..KEYING_MATERIAL_LEN],
128        )
129    }
130
131    /// Generate random keying material and return (context, base64-key).
132    pub fn generate() -> Result<(Self, String)> {
133        use base64::Engine;
134        let material: [u8; KEYING_MATERIAL_LEN] = rand::random();
135        let b64 = base64::engine::general_purpose::STANDARD.encode(material);
136        let ctx = Self::new(&material[..MASTER_KEY_LEN], &material[MASTER_KEY_LEN..])?;
137        Ok((ctx, b64))
138    }
139
140    /// Encrypt and authenticate an RTP packet.
141    pub fn protect_rtp(&mut self, rtp_packet: &[u8]) -> Result<Vec<u8>> {
142        if rtp_packet.len() < 12 {
143            return Err(Error::rtp("RTP packet too short"));
144        }
145
146        let cc = (rtp_packet[0] & 0x0F) as usize;
147        let header_len = 12 + cc * 4;
148        if rtp_packet.len() < header_len {
149            return Err(Error::rtp("RTP packet truncated"));
150        }
151
152        let seq = u16::from_be_bytes([rtp_packet[2], rtp_packet[3]]);
153        let ssrc =
154            u32::from_be_bytes([rtp_packet[8], rtp_packet[9], rtp_packet[10], rtp_packet[11]]);
155
156        // Update ROC for sending
157        if !self.initialized {
158            self.s_l = seq;
159            self.initialized = true;
160        } else if seq == 0 && self.s_l == 0xFFFF {
161            self.roc = self.roc.wrapping_add(1);
162        }
163        self.s_l = seq;
164
165        let iv = build_iv(&self.session_salt, ssrc, self.roc, seq);
166
167        let mut output = rtp_packet.to_vec();
168        aes_128_cm_encrypt(&self.session_key, &iv, &mut output[header_len..])?;
169
170        let tag = compute_rtp_auth_tag(&self.session_auth_key, &output, self.roc)?;
171        output.extend_from_slice(&tag);
172
173        Ok(output)
174    }
175
176    /// Verify and decrypt an SRTP packet.
177    pub fn unprotect_rtp(&mut self, srtp_packet: &[u8]) -> Result<Vec<u8>> {
178        if srtp_packet.len() < 12 + AUTH_TAG_LEN {
179            return Err(Error::srtp("SRTP packet too short"));
180        }
181
182        let cc = (srtp_packet[0] & 0x0F) as usize;
183        let header_len = 12 + cc * 4;
184        if srtp_packet.len() < header_len + AUTH_TAG_LEN {
185            return Err(Error::srtp("SRTP packet truncated"));
186        }
187
188        let seq = u16::from_be_bytes([srtp_packet[2], srtp_packet[3]]);
189        let ssrc = u32::from_be_bytes([
190            srtp_packet[8],
191            srtp_packet[9],
192            srtp_packet[10],
193            srtp_packet[11],
194        ]);
195
196        let estimated_roc = self.estimate_roc(seq);
197
198        let auth_boundary = srtp_packet.len() - AUTH_TAG_LEN;
199        let authenticated_portion = &srtp_packet[..auth_boundary];
200        let received_tag = &srtp_packet[auth_boundary..];
201
202        let computed_tag =
203            compute_rtp_auth_tag(&self.session_auth_key, authenticated_portion, estimated_roc)?;
204        if !constant_time_eq(&computed_tag, received_tag) {
205            return Err(Error::srtp("SRTP authentication failed"));
206        }
207
208        self.update_roc(seq);
209
210        let iv = build_iv(&self.session_salt, ssrc, estimated_roc, seq);
211        let mut output = authenticated_portion.to_vec();
212        aes_128_cm_encrypt(&self.session_key, &iv, &mut output[header_len..])?;
213
214        Ok(output)
215    }
216
217    /// Encrypt and authenticate an RTCP packet.
218    pub fn protect_rtcp(&mut self, rtcp_packet: &[u8]) -> Result<Vec<u8>> {
219        if rtcp_packet.len() < 8 {
220            return Err(Error::rtcp("RTCP packet too short"));
221        }
222
223        let ssrc = u32::from_be_bytes([
224            rtcp_packet[4],
225            rtcp_packet[5],
226            rtcp_packet[6],
227            rtcp_packet[7],
228        ]);
229
230        let index = self.srtcp_index;
231        self.srtcp_index = self.srtcp_index.wrapping_add(1) & 0x7FFF_FFFF;
232
233        let iv = build_rtcp_iv(&self.rtcp_session_salt, ssrc, index);
234
235        let mut output = rtcp_packet.to_vec();
236        if output.len() > 8 {
237            aes_128_cm_encrypt(&self.rtcp_session_key, &iv, &mut output[8..])?;
238        }
239
240        let e_index = index | 0x8000_0000;
241        output.extend_from_slice(&e_index.to_be_bytes());
242
243        let tag = compute_rtcp_auth_tag(&self.rtcp_session_auth_key, &output)?;
244        output.extend_from_slice(&tag);
245
246        Ok(output)
247    }
248
249    /// Verify and decrypt an SRTCP packet.
250    pub fn unprotect_rtcp(&mut self, srtcp_packet: &[u8]) -> Result<Vec<u8>> {
251        if srtcp_packet.len() < 8 + 4 + AUTH_TAG_LEN {
252            return Err(Error::srtp("SRTCP packet too short"));
253        }
254
255        let auth_boundary = srtcp_packet.len() - AUTH_TAG_LEN;
256        let authenticated_portion = &srtcp_packet[..auth_boundary];
257        let received_tag = &srtcp_packet[auth_boundary..];
258
259        let computed_tag =
260            compute_rtcp_auth_tag(&self.rtcp_session_auth_key, authenticated_portion)?;
261        if !constant_time_eq(&computed_tag, received_tag) {
262            return Err(Error::srtp("SRTCP authentication failed"));
263        }
264
265        let index_offset = authenticated_portion.len() - 4;
266        let e_index = u32::from_be_bytes([
267            authenticated_portion[index_offset],
268            authenticated_portion[index_offset + 1],
269            authenticated_portion[index_offset + 2],
270            authenticated_portion[index_offset + 3],
271        ]);
272        let encrypted = (e_index & 0x8000_0000) != 0;
273        let index = e_index & 0x7FFF_FFFF;
274
275        let mut output = authenticated_portion[..index_offset].to_vec();
276
277        if encrypted && output.len() > 8 {
278            let ssrc = u32::from_be_bytes([output[4], output[5], output[6], output[7]]);
279            let iv = build_rtcp_iv(&self.rtcp_session_salt, ssrc, index);
280            aes_128_cm_encrypt(&self.rtcp_session_key, &iv, &mut output[8..])?;
281        }
282
283        Ok(output)
284    }
285
286    /// Estimate the ROC (Rollover Counter) for a received sequence number.
287    ///
288    /// RFC 3711 Appendix A: The algorithm uses signed arithmetic to detect
289    /// whether a sequence number has wrapped around.
290    fn estimate_roc(&self, seq: u16) -> u32 {
291        if !self.initialized {
292            return 0;
293        }
294
295        // Compute signed difference between seq and last seen sequence
296        // This correctly handles the 16-bit wrap-around
297        let diff = (seq as i32) - (self.s_l as i32);
298
299        if diff > 0x8000 {
300            // seq appears much larger than s_l, but it's actually a late packet
301            // from before the current ROC (seq wrapped backward from our perspective)
302            self.roc.wrapping_sub(1)
303        } else if diff < -0x8000 {
304            // s_l appears much larger than seq, meaning seq has rolled over
305            // to a new ROC cycle (seq wrapped forward)
306            self.roc.wrapping_add(1)
307        } else {
308            // Normal case: seq and s_l are within half the sequence space
309            self.roc
310        }
311    }
312
313    /// Update ROC state after successfully authenticating a packet.
314    fn update_roc(&mut self, seq: u16) {
315        if !self.initialized {
316            self.s_l = seq;
317            self.initialized = true;
318            return;
319        }
320
321        let estimated = self.estimate_roc(seq);
322
323        // Update ROC if it changed
324        if estimated != self.roc {
325            self.roc = estimated;
326        }
327
328        // Update s_l (highest sequence seen in current ROC)
329        // Use signed difference to handle rollover edge case
330        let diff = (seq as i32) - (self.s_l as i32);
331        if diff > 0 || diff < -0x8000 {
332            // seq > s_l normally, OR seq rolled over to new ROC
333            self.s_l = seq;
334        }
335    }
336}
337
338impl std::fmt::Debug for SrtpContext {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        f.debug_struct("SrtpContext")
341            .field("initialized", &self.initialized)
342            .field("roc", &self.roc)
343            .field("s_l", &self.s_l)
344            .finish()
345    }
346}
347
348fn build_iv(session_salt: &[u8; SESSION_SALT_LEN], ssrc: u32, roc: u32, seq: u16) -> [u8; 16] {
349    let mut iv = [0u8; 16];
350    iv[4..8].copy_from_slice(&ssrc.to_be_bytes());
351    iv[8..12].copy_from_slice(&roc.to_be_bytes());
352    iv[12..14].copy_from_slice(&seq.to_be_bytes());
353    for i in 0..SESSION_SALT_LEN {
354        iv[2 + i] ^= session_salt[i];
355    }
356    iv
357}
358
359fn build_rtcp_iv(session_salt: &[u8; SESSION_SALT_LEN], ssrc: u32, index: u32) -> [u8; 16] {
360    let mut iv = [0u8; 16];
361    iv[4..8].copy_from_slice(&ssrc.to_be_bytes());
362    iv[10..14].copy_from_slice(&index.to_be_bytes());
363    for i in 0..SESSION_SALT_LEN {
364        iv[2 + i] ^= session_salt[i];
365    }
366    iv
367}
368
369fn aes_128_cm_encrypt(key: &[u8; 16], iv: &[u8; 16], data: &mut [u8]) -> Result<()> {
370    let mut cipher = Aes128Ctr::new(key.into(), iv.into());
371    cipher.apply_keystream(data);
372    Ok(())
373}
374
375fn compute_rtp_auth_tag(
376    auth_key: &[u8; SESSION_AUTH_KEY_LEN],
377    authenticated_portion: &[u8],
378    roc: u32,
379) -> Result<[u8; AUTH_TAG_LEN]> {
380    let mut mac = HmacSha1::new_from_slice(auth_key)
381        .map_err(|e| Error::srtp(format!("HMAC init error: {}", e)))?;
382    mac.update(authenticated_portion);
383    mac.update(&roc.to_be_bytes());
384    let result = mac.finalize().into_bytes();
385    let mut tag = [0u8; AUTH_TAG_LEN];
386    tag.copy_from_slice(&result[..AUTH_TAG_LEN]);
387    Ok(tag)
388}
389
390fn compute_rtcp_auth_tag(
391    auth_key: &[u8; SESSION_AUTH_KEY_LEN],
392    authenticated_portion: &[u8],
393) -> Result<[u8; AUTH_TAG_LEN]> {
394    let mut mac = HmacSha1::new_from_slice(auth_key)
395        .map_err(|e| Error::srtp(format!("HMAC init error: {}", e)))?;
396    mac.update(authenticated_portion);
397    let result = mac.finalize().into_bytes();
398    let mut tag = [0u8; AUTH_TAG_LEN];
399    tag.copy_from_slice(&result[..AUTH_TAG_LEN]);
400    Ok(tag)
401}
402
403fn derive_session_key(
404    master_key: &[u8],
405    master_salt: &[u8],
406    label: u8,
407) -> Result<[u8; SESSION_KEY_LEN]> {
408    let mut output = [0u8; SESSION_KEY_LEN];
409    prf_aes_cm(master_key, master_salt, label, &mut output)?;
410    Ok(output)
411}
412
413fn derive_session_salt(
414    master_key: &[u8],
415    master_salt: &[u8],
416    label: u8,
417) -> Result<[u8; SESSION_SALT_LEN]> {
418    let mut output = [0u8; SESSION_SALT_LEN];
419    prf_aes_cm(master_key, master_salt, label, &mut output)?;
420    Ok(output)
421}
422
423fn derive_session_auth_key(
424    master_key: &[u8],
425    master_salt: &[u8],
426    label: u8,
427) -> Result<[u8; SESSION_AUTH_KEY_LEN]> {
428    let mut output = [0u8; SESSION_AUTH_KEY_LEN];
429    prf_aes_cm(master_key, master_salt, label, &mut output)?;
430    Ok(output)
431}
432
433fn prf_aes_cm(master_key: &[u8], master_salt: &[u8], label: u8, output: &mut [u8]) -> Result<()> {
434    let mut x = [0u8; 14];
435    x[7] = label;
436
437    for i in 0..14 {
438        x[i] ^= master_salt[i];
439    }
440
441    let mut iv = [0u8; 16];
442    iv[..14].copy_from_slice(&x);
443
444    output.fill(0);
445    let mut cipher = Aes128Ctr::new(master_key.into(), (&iv).into());
446    cipher.apply_keystream(output);
447
448    Ok(())
449}
450
451fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
452    if a.len() != b.len() {
453        return false;
454    }
455    let mut diff = 0u8;
456    for (x, y) in a.iter().zip(b.iter()) {
457        diff |= x ^ y;
458    }
459    diff == 0
460}
461
462/// Parse an `a=crypto` attribute from SDP.
463pub fn parse_sdp_crypto(sdp: &str) -> Option<String> {
464    for line in sdp.lines() {
465        let line = line.trim();
466        if let Some(rest) = line.strip_prefix("a=crypto:") {
467            let parts: Vec<&str> = rest.splitn(3, ' ').collect();
468            if parts.len() >= 3
469                && parts[1] == "AES_CM_128_HMAC_SHA1_80"
470                && parts[2].starts_with("inline:")
471            {
472                let key_material = &parts[2]["inline:".len()..];
473                let key_material = key_material.split('|').next().unwrap_or(key_material);
474                return Some(key_material.to_string());
475            }
476        }
477    }
478    None
479}
480
481/// Build the `a=crypto` SDP attribute line.
482pub fn build_sdp_crypto_line(b64_key: &str) -> String {
483    format!("a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:{}\r\n", b64_key)
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[test]
491    fn test_roundtrip_rtp() {
492        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
493        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
494
495        let mut rtp = vec![0x80, 0x00];
496        rtp.extend_from_slice(&1u16.to_be_bytes());
497        rtp.extend_from_slice(&160u32.to_be_bytes());
498        rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
499        rtp.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
500
501        let srtp = ctx_send.protect_rtp(&rtp).unwrap();
502        assert_ne!(&srtp[12..12 + 4], &[0xDE, 0xAD, 0xBE, 0xEF]);
503        assert_eq!(srtp.len(), rtp.len() + AUTH_TAG_LEN);
504
505        let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
506        assert_eq!(decrypted, rtp);
507    }
508
509    #[test]
510    fn test_roundtrip_rtcp() {
511        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
512        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
513
514        let mut rtcp = vec![0x80, 200];
515        rtcp.extend_from_slice(&6u16.to_be_bytes());
516        rtcp.extend_from_slice(&0xAABBCCDDu32.to_be_bytes());
517        rtcp.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
518
519        let srtcp = ctx_send.protect_rtcp(&rtcp).unwrap();
520        let decrypted = ctx_recv.unprotect_rtcp(&srtcp).unwrap();
521        assert_eq!(decrypted, rtcp);
522    }
523
524    #[test]
525    fn test_auth_failure() {
526        let (mut ctx_send, _) = SrtpContext::generate().unwrap();
527        let (_, b64_other) = SrtpContext::generate().unwrap();
528        let mut ctx_recv = SrtpContext::from_base64(&b64_other).unwrap();
529
530        let mut rtp = vec![0x80, 0x00];
531        rtp.extend_from_slice(&1u16.to_be_bytes());
532        rtp.extend_from_slice(&160u32.to_be_bytes());
533        rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
534        rtp.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
535
536        let srtp = ctx_send.protect_rtp(&rtp).unwrap();
537        assert!(ctx_recv.unprotect_rtp(&srtp).is_err());
538    }
539
540    #[test]
541    fn test_parse_sdp_crypto() {
542        let sdp = "v=0\r\n\
543                   a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:dGVzdGtleXRlc3RrZXkxMjM0NTY3ODkwMTI=\r\n";
544        let key = parse_sdp_crypto(sdp);
545        assert_eq!(
546            key,
547            Some("dGVzdGtleXRlc3RrZXkxMjM0NTY3ODkwMTI=".to_string())
548        );
549    }
550
551    #[test]
552    fn test_parse_sdp_crypto_with_lifetime() {
553        // SDP with MKI and lifetime parameters
554        let sdp = "v=0\r\n\
555                   a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJjRGVmZ0hpSktsbU5PUHF|2^31\r\n";
556        let key = parse_sdp_crypto(sdp);
557        assert_eq!(key, Some("YUJjRGVmZ0hpSktsbU5PUHF".to_string()));
558    }
559
560    #[test]
561    fn test_parse_sdp_crypto_no_crypto_line() {
562        let sdp = "v=0\r\nm=audio 5004 RTP/AVP 0\r\n";
563        assert!(parse_sdp_crypto(sdp).is_none());
564    }
565
566    #[test]
567    fn test_parse_sdp_crypto_wrong_suite() {
568        let sdp = "v=0\r\na=crypto:1 AES_CM_256_HMAC_SHA1_80 inline:key=\r\n";
569        assert!(parse_sdp_crypto(sdp).is_none());
570    }
571
572    #[test]
573    fn test_build_sdp_crypto_line() {
574        let key = "YUJjRGVmZ0hpSktsbU5PUHF";
575        let line = build_sdp_crypto_line(key);
576        assert_eq!(
577            line,
578            "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJjRGVmZ0hpSktsbU5PUHF\r\n"
579        );
580    }
581
582    #[test]
583    fn test_srtp_sequence_rollover() {
584        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
585        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
586
587        // Start near rollover and go through it sequentially
588        // This simulates a ~22 minute call at 50 packets/second
589        let start_seq = 65530u16;
590        for i in 0u16..12 {
591            let seq = start_seq.wrapping_add(i);
592            let ts = (start_seq as u32 + i as u32) * 160;
593
594            let mut rtp = vec![0x80, 0x00];
595            rtp.extend_from_slice(&seq.to_be_bytes());
596            rtp.extend_from_slice(&ts.to_be_bytes());
597            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
598            rtp.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
599
600            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
601            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
602            assert_eq!(decrypted, rtp, "Failed at seq {} (i={})", seq, i);
603        }
604    }
605
606    #[test]
607    fn test_srtp_continuous_through_rollover() {
608        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
609        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
610
611        // Simulate continuous packet flow through a rollover boundary
612        // Start near max and go through rollover (realistic 22+ minute call)
613        let start = 65520u16;
614        for i in 0u16..32 {
615            let seq = start.wrapping_add(i);
616            let ts = (start as u32 + i as u32) * 160;
617
618            let mut rtp = vec![0x80, 0x00];
619            rtp.extend_from_slice(&seq.to_be_bytes());
620            rtp.extend_from_slice(&ts.to_be_bytes());
621            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
622            rtp.extend_from_slice(&[(i & 0xFF) as u8; 4]);
623
624            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
625            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
626            assert_eq!(decrypted, rtp, "Failed at seq {} (i={})", seq, i);
627        }
628
629        // Verify ROC state - sender should have incremented ROC
630        assert_eq!(ctx_send.roc, 1, "Sender ROC should be 1 after rollover");
631        assert_eq!(ctx_recv.roc, 1, "Receiver ROC should be 1 after rollover");
632    }
633
634    #[test]
635    fn test_srtp_second_rollover() {
636        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
637        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
638
639        // First rollover
640        let start1 = 65530u16;
641        for i in 0u16..12 {
642            let seq = start1.wrapping_add(i);
643            let ts = (start1 as u32 + i as u32) * 160;
644
645            let mut rtp = vec![0x80, 0x00];
646            rtp.extend_from_slice(&seq.to_be_bytes());
647            rtp.extend_from_slice(&ts.to_be_bytes());
648            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
649            rtp.extend_from_slice(&[0xAA; 4]);
650
651            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
652            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
653            assert_eq!(decrypted, rtp, "First rollover failed at seq {}", seq);
654        }
655
656        assert_eq!(ctx_send.roc, 1);
657        assert_eq!(ctx_recv.roc, 1);
658
659        // Continue from where we left off (seq 6 after rollover from 65535 to 0)
660        // Now go through second rollover
661        let current_seq = start1.wrapping_add(12); // Should be 6
662        let start2 = 65530u16;
663
664        // Need to send packets continuously to get to second rollover
665        // From seq 6 to seq 65530, then through to seq 6 again
666        for seq in current_seq..start2 {
667            let ts = (65530u32 + 12 + (seq - current_seq) as u32) * 160;
668
669            let mut rtp = vec![0x80, 0x00];
670            rtp.extend_from_slice(&seq.to_be_bytes());
671            rtp.extend_from_slice(&ts.to_be_bytes());
672            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
673            rtp.extend_from_slice(&[0xBB; 4]);
674
675            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
676            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
677            assert_eq!(decrypted, rtp, "Gap packet failed at seq {}", seq);
678        }
679
680        // ROC should still be 1 (no rollover yet)
681        assert_eq!(ctx_send.roc, 1);
682        assert_eq!(ctx_recv.roc, 1);
683
684        // Now second rollover: 65530 -> 65535 -> 0 -> 5
685        for i in 0u16..12 {
686            let seq = start2.wrapping_add(i);
687            let ts = (65530u32 * 2 + i as u32) * 160;
688
689            let mut rtp = vec![0x80, 0x00];
690            rtp.extend_from_slice(&seq.to_be_bytes());
691            rtp.extend_from_slice(&ts.to_be_bytes());
692            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
693            rtp.extend_from_slice(&[0xCC; 4]);
694
695            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
696            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
697            assert_eq!(decrypted, rtp, "Second rollover failed at seq {}", seq);
698        }
699
700        // After second rollover
701        assert_eq!(
702            ctx_send.roc, 2,
703            "Sender ROC should be 2 after second rollover"
704        );
705        assert_eq!(
706            ctx_recv.roc, 2,
707            "Receiver ROC should be 2 after second rollover"
708        );
709    }
710
711    #[test]
712    fn test_srtp_out_of_order_near_rollover() {
713        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
714        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
715
716        // Build packets around rollover but send slightly out of order
717        let sequences = [65534u16, 65535, 0, 1, 2, 3];
718        let mut packets: Vec<(u16, Vec<u8>)> = Vec::new();
719
720        for (i, &seq) in sequences.iter().enumerate() {
721            let ts = (65534u32 + i as u32) * 160;
722            let mut rtp = vec![0x80, 0x00];
723            rtp.extend_from_slice(&seq.to_be_bytes());
724            rtp.extend_from_slice(&ts.to_be_bytes());
725            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
726            rtp.extend_from_slice(&[i as u8; 4]);
727
728            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
729            packets.push((seq, srtp));
730        }
731
732        // Receive in slightly different order: 65534, 65535, 1, 0, 2, 3
733        // (swap 0 and 1 to test reordering across rollover)
734        let receive_order = [0, 1, 3, 2, 4, 5];
735        for &idx in &receive_order {
736            let (seq, ref srtp) = packets[idx];
737            let result = ctx_recv.unprotect_rtp(srtp);
738            assert!(
739                result.is_ok(),
740                "Failed to decrypt seq {} (idx {})",
741                seq,
742                idx
743            );
744        }
745    }
746
747    #[test]
748    fn test_srtp_multiple_packets() {
749        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
750        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
751
752        // Send multiple packets
753        for seq in 0u16..100 {
754            let mut rtp = vec![0x80, 0x00];
755            rtp.extend_from_slice(&seq.to_be_bytes());
756            rtp.extend_from_slice(&((seq as u32) * 160).to_be_bytes());
757            rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
758            rtp.extend_from_slice(&[seq as u8; 160]);
759
760            let srtp = ctx_send.protect_rtp(&rtp).unwrap();
761            let decrypted = ctx_recv.unprotect_rtp(&srtp).unwrap();
762            assert_eq!(decrypted, rtp);
763        }
764    }
765
766    #[test]
767    fn test_srtp_invalid_key_length() {
768        // Too short master key
769        let result = SrtpContext::new(&[0u8; 15], &[0u8; 14]);
770        assert!(result.is_err());
771
772        // Too short master salt
773        let result = SrtpContext::new(&[0u8; 16], &[0u8; 13]);
774        assert!(result.is_err());
775    }
776
777    #[test]
778    fn test_srtp_invalid_base64() {
779        let result = SrtpContext::from_base64("not-valid-base64!!!");
780        assert!(result.is_err());
781    }
782
783    #[test]
784    fn test_srtp_too_short_base64() {
785        let result = SrtpContext::from_base64("YWJjZA=="); // Only 4 bytes
786        assert!(result.is_err());
787    }
788
789    #[test]
790    fn test_srtp_packet_too_short() {
791        let (mut ctx, _) = SrtpContext::generate().unwrap();
792
793        // RTP packet too short
794        let short_rtp = vec![0x80, 0x00, 0x00, 0x01];
795        assert!(ctx.protect_rtp(&short_rtp).is_err());
796
797        // SRTP packet too short for auth tag
798        let short_srtp = vec![
799            0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xA0, 0x12, 0x34, 0x56, 0x78,
800        ];
801        assert!(ctx.unprotect_rtp(&short_srtp).is_err());
802    }
803
804    #[test]
805    fn test_srtp_tampered_packet() {
806        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
807        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
808
809        let mut rtp = vec![0x80, 0x00];
810        rtp.extend_from_slice(&1u16.to_be_bytes());
811        rtp.extend_from_slice(&160u32.to_be_bytes());
812        rtp.extend_from_slice(&0x12345678u32.to_be_bytes());
813        rtp.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
814
815        let mut srtp = ctx_send.protect_rtp(&rtp).unwrap();
816
817        // Tamper with payload
818        srtp[12] ^= 0xFF;
819
820        assert!(ctx_recv.unprotect_rtp(&srtp).is_err());
821    }
822
823    #[test]
824    fn test_srtcp_multiple_packets() {
825        let (mut ctx_send, b64) = SrtpContext::generate().unwrap();
826        let mut ctx_recv = SrtpContext::from_base64(&b64).unwrap();
827
828        // Send multiple RTCP packets
829        for i in 0..10 {
830            let mut rtcp = vec![0x80, 200];
831            rtcp.extend_from_slice(&6u16.to_be_bytes());
832            rtcp.extend_from_slice(&(0xAABBCC00u32 + i).to_be_bytes());
833            rtcp.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
834
835            let srtcp = ctx_send.protect_rtcp(&rtcp).unwrap();
836            let decrypted = ctx_recv.unprotect_rtcp(&srtcp).unwrap();
837            assert_eq!(decrypted, rtcp);
838        }
839    }
840
841    #[test]
842    fn test_rtcp_too_short() {
843        let (mut ctx, _) = SrtpContext::generate().unwrap();
844
845        // RTCP packet too short
846        let short_rtcp = vec![0x80, 200, 0x00, 0x01];
847        assert!(ctx.protect_rtcp(&short_rtcp).is_err());
848    }
849
850    #[test]
851    fn test_srtp_context_debug() {
852        let (ctx, _) = SrtpContext::generate().unwrap();
853        let debug_str = format!("{:?}", ctx);
854        assert!(debug_str.contains("SrtpContext"));
855        assert!(debug_str.contains("initialized"));
856    }
857
858    #[test]
859    fn test_constant_time_eq() {
860        assert!(constant_time_eq(&[1, 2, 3], &[1, 2, 3]));
861        assert!(!constant_time_eq(&[1, 2, 3], &[1, 2, 4]));
862        assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
863        assert!(constant_time_eq(&[], &[]));
864    }
865}