rvoip-rtp-core 0.2.3

RTP/RTCP protocol implementation for the rvoip stack
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
//! SDES (Security DEScriptions) implementation
//!
//! This module implements the Security Descriptions for SDP as defined in RFC 4568.
//! SDES allows keys and related information to be transported over SDP.
//!
//! Reference: <https://tools.ietf.org/html/rfc4568>

use crate::security::SecurityKeyExchange;
use crate::srtp::crypto::SrtpCryptoKey;
use crate::srtp::{SrtpCryptoSuite, SRTP_AES128_CM_SHA1_32, SRTP_AES128_CM_SHA1_80};
use crate::Error;
use base64;
use rand::{rngs::OsRng, RngCore};

/// SDES crypto attribute representation
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdesCryptoAttribute {
    /// Tag (unique for each crypto attribute)
    pub tag: u32,
    /// Crypto-suite (e.g., "AES_CM_128_HMAC_SHA1_80")
    pub crypto_suite: String,
    /// Key method (always "inline" for SDES)
    pub key_method: String,
    /// Key information
    pub key_info: String,
    /// Optional session parameters
    pub session_params: Vec<String>,
}

impl SdesCryptoAttribute {
    /// Create a new SDES crypto attribute
    pub fn new(tag: u32, crypto_suite: &str, key_info: &str) -> Self {
        Self {
            tag,
            crypto_suite: crypto_suite.to_string(),
            key_method: "inline".to_string(),
            key_info: key_info.to_string(),
            session_params: Vec::new(),
        }
    }

    /// Add a session parameter
    pub fn add_session_param(&mut self, param: &str) {
        self.session_params.push(param.to_string());
    }

    /// Format the crypto attribute as a string for SDP
    pub fn to_string(&self) -> String {
        let mut result = format!(
            "{} {} {}:{}",
            self.tag, self.crypto_suite, self.key_method, self.key_info
        );

        // Add session parameters
        if !self.session_params.is_empty() {
            result.push_str(" ");
            result.push_str(&self.session_params.join(";"));
        }

        result
    }

    /// Parse a crypto attribute from a string
    pub fn parse(s: &str) -> Result<Self, Error> {
        let parts: Vec<&str> = s.split_whitespace().collect();

        if parts.len() < 3 {
            return Err(Error::ParseError(
                "Invalid SDES crypto attribute format".into(),
            ));
        }

        // Parse tag
        let tag = parts[0]
            .parse::<u32>()
            .map_err(|_| Error::ParseError("Invalid tag in SDES crypto attribute".into()))?;

        // Parse crypto suite
        let crypto_suite = parts[1].to_string();

        // Parse key method and key info
        let key_parts: Vec<&str> = parts[2].split(':').collect();
        if key_parts.len() != 2 {
            return Err(Error::ParseError(
                "Invalid key format in SDES crypto attribute".into(),
            ));
        }

        let key_method = key_parts[0].to_string();
        let key_info = key_parts[1].to_string();

        if key_method != "inline" {
            return Err(Error::ParseError(
                "Only 'inline' key method is supported".into(),
            ));
        }

        // Parse session parameters
        let mut session_params = Vec::new();
        if parts.len() > 3 {
            let params_str = parts[3..].join(" ");
            for param in params_str.split(';') {
                session_params.push(param.trim().to_string());
            }
        }

        Ok(Self {
            tag,
            crypto_suite,
            key_method,
            key_info,
            session_params,
        })
    }
}

/// SDES role in key exchange
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdesRole {
    /// Offerer (creates initial crypto attributes)
    Offerer,
    /// Answerer (responds to crypto attributes)
    Answerer,
}

/// SDES state
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SdesState {
    /// Initial state
    Initial,
    /// Offer sent
    OfferSent,
    /// Answer received
    AnswerReceived,
    /// Completed
    Completed,
}

/// SDES configuration
#[derive(Debug, Clone)]
pub struct SdesConfig {
    /// List of supported SRTP crypto suites in order of preference
    pub crypto_suites: Vec<SrtpCryptoSuite>,
    /// Number of crypto attributes to include in offer
    pub offer_count: usize,
}

impl Default for SdesConfig {
    fn default() -> Self {
        Self {
            crypto_suites: vec![SRTP_AES128_CM_SHA1_80, SRTP_AES128_CM_SHA1_32],
            offer_count: 2,
        }
    }
}

/// SDES implementation
pub struct Sdes {
    /// Configuration
    config: SdesConfig,
    /// Role (offerer or answerer)
    role: SdesRole,
    /// Current state
    state: SdesState,
    /// Local crypto attributes
    local_attrs: Vec<SdesCryptoAttribute>,
    /// Remote crypto attributes
    remote_attrs: Vec<SdesCryptoAttribute>,
    /// Selected crypto attribute
    selected_attr: Option<SdesCryptoAttribute>,
    /// Negotiated SRTP crypto key
    srtp_key: Option<SrtpCryptoKey>,
    /// Negotiated SRTP crypto suite
    srtp_suite: Option<SrtpCryptoSuite>,
}

impl Sdes {
    /// Create a new SDES instance
    pub fn new(config: SdesConfig, role: SdesRole) -> Self {
        Self {
            config,
            role,
            state: SdesState::Initial,
            local_attrs: Vec::new(),
            remote_attrs: Vec::new(),
            selected_attr: None,
            srtp_key: None,
            srtp_suite: None,
        }
    }

    /// Create a crypto attribute for a specific crypto suite
    fn create_crypto_attribute(
        &self,
        tag: u32,
        suite: &SrtpCryptoSuite,
    ) -> Result<(SdesCryptoAttribute, SrtpCryptoKey), Error> {
        // Map SRTP crypto suite to SDES crypto suite string
        let crypto_suite_str = match (suite.encryption, suite.authentication) {
            (
                crate::srtp::SrtpEncryptionAlgorithm::AesCm,
                crate::srtp::SrtpAuthenticationAlgorithm::HmacSha1_80,
            ) => "AES_CM_128_HMAC_SHA1_80",
            (
                crate::srtp::SrtpEncryptionAlgorithm::AesCm,
                crate::srtp::SrtpAuthenticationAlgorithm::HmacSha1_32,
            ) => "AES_CM_128_HMAC_SHA1_32",
            _ => {
                return Err(Error::UnsupportedFeature(
                    "Unsupported SRTP crypto suite for SDES".into(),
                ))
            }
        };

        // Generate random key
        let mut key = vec![0u8; 16]; // 128-bit AES key
        OsRng.fill_bytes(&mut key);

        // Generate random salt
        let mut salt = vec![0u8; 14]; // 112-bit salt
        OsRng.fill_bytes(&mut salt);

        // Combine key and salt
        let mut keysalt = Vec::with_capacity(key.len() + salt.len());
        keysalt.extend_from_slice(&key);
        keysalt.extend_from_slice(&salt);

        // Base64 encode key+salt
        let key_info = base64::encode(&keysalt);

        // Create crypto attribute
        let attr = SdesCryptoAttribute::new(tag, crypto_suite_str, &key_info);

        // Create SRTP key for later use
        let srtp_key = SrtpCryptoKey::new(key, salt);

        Ok((attr, srtp_key))
    }

    /// Create offer crypto attributes
    fn create_offer(&mut self) -> Result<Vec<String>, Error> {
        if self.role != SdesRole::Offerer {
            return Err(Error::InvalidState("Only offerer can create offer".into()));
        }

        let mut offer = Vec::new();

        // Create crypto attributes for each supported crypto suite
        for (i, suite) in self
            .config
            .crypto_suites
            .iter()
            .take(self.config.offer_count)
            .enumerate()
        {
            let tag = (i + 1) as u32;
            let (attr, srtp_key) = self.create_crypto_attribute(tag, suite)?;

            // Store local attribute
            self.local_attrs.push(attr.clone());

            // Add to offer
            offer.push(format!("a=crypto:{}", attr.to_string()));

            // Save key if it's the first one (the default)
            if i == 0 {
                self.srtp_key = Some(srtp_key);
                self.srtp_suite = Some(suite.clone());
            }
        }

        // Update state
        self.state = SdesState::OfferSent;

        Ok(offer)
    }

    /// Parse offer and create answer
    fn create_answer(&mut self, offer: &[String]) -> Result<Vec<String>, Error> {
        if self.role != SdesRole::Answerer {
            return Err(Error::InvalidState(
                "Only answerer can create answer".into(),
            ));
        }

        // Parse offer
        for line in offer {
            if line.starts_with("a=crypto:") {
                let attr_str = line.trim_start_matches("a=crypto:");
                let attr = SdesCryptoAttribute::parse(attr_str)?;

                // Store remote attribute
                self.remote_attrs.push(attr);
            }
        }

        if self.remote_attrs.is_empty() {
            return Err(Error::InvalidMessage(
                "No crypto attributes in offer".into(),
            ));
        }

        // Select the first supported crypto attribute
        let selected = &self.remote_attrs[0];

        // Map SDES crypto suite to SRTP crypto suite
        let srtp_suite = match selected.crypto_suite.as_str() {
            "AES_CM_128_HMAC_SHA1_80" => SRTP_AES128_CM_SHA1_80,
            "AES_CM_128_HMAC_SHA1_32" => SRTP_AES128_CM_SHA1_32,
            _ => {
                return Err(Error::UnsupportedFeature(format!(
                    "Unsupported crypto suite: {}",
                    selected.crypto_suite
                )))
            }
        };

        // Parse key info
        let key_info = selected.key_info.as_str();

        // Base64 decode key+salt
        let keysalt = base64::decode(key_info)
            .map_err(|_| Error::ParseError("Invalid Base64 encoding in key info".into()))?;

        if keysalt.len() < 30 {
            return Err(Error::ParseError("Key info too short".into()));
        }

        // Split key and salt
        let key = keysalt[0..16].to_vec();
        let salt = keysalt[16..30].to_vec();

        // Store key for later use
        self.srtp_key = Some(SrtpCryptoKey::new(key, salt));
        self.srtp_suite = Some(srtp_suite);
        self.selected_attr = Some(selected.clone());

        // Create answer
        let mut answer = Vec::new();
        answer.push(format!("a=crypto:{}", selected.to_string()));

        // Update state
        self.state = SdesState::Completed;

        Ok(answer)
    }

    /// Process answer
    fn process_answer(&mut self, answer: &[String]) -> Result<(), Error> {
        if self.role != SdesRole::Offerer {
            return Err(Error::InvalidState(
                "Only offerer can process answer".into(),
            ));
        }

        // Parse answer
        let mut selected_attr = None;

        for line in answer {
            if line.starts_with("a=crypto:") {
                let attr_str = line.trim_start_matches("a=crypto:");
                let attr = SdesCryptoAttribute::parse(attr_str)?;

                // Store remote attribute
                self.remote_attrs.push(attr.clone());

                // This is the selected attribute
                selected_attr = Some(attr);
                break;
            }
        }

        if selected_attr.is_none() {
            return Err(Error::InvalidMessage(
                "No crypto attributes in answer".into(),
            ));
        }

        let selected = selected_attr.unwrap();

        // Find matching local attribute by tag
        let local_attr = self.local_attrs.iter().find(|a| a.tag == selected.tag);

        if local_attr.is_none() {
            return Err(Error::InvalidMessage(format!(
                "No matching local attribute for tag {}",
                selected.tag
            )));
        }

        // Store selected attribute
        self.selected_attr = Some(selected);

        // Update state
        self.state = SdesState::Completed;

        Ok(())
    }
}

impl SecurityKeyExchange for Sdes {
    fn init(&mut self) -> Result<(), Error> {
        // No initialization needed for SDES
        Ok(())
    }

    fn process_message(&mut self, message: &[u8]) -> Result<Option<Vec<u8>>, Error> {
        // SDES messages are SDP lines
        let message_str = std::str::from_utf8(message)
            .map_err(|_| Error::ParseError("Invalid UTF-8 in SDES message".into()))?;

        let lines: Vec<String> = message_str.lines().map(|s| s.trim().to_string()).collect();

        match (self.role, &self.state) {
            (SdesRole::Offerer, SdesState::Initial) => {
                // Create offer
                let offer = self.create_offer()?;
                let offer_str = offer.join("\r\n");
                Ok(Some(offer_str.into_bytes()))
            }
            (SdesRole::Offerer, SdesState::OfferSent) => {
                // Process answer
                self.process_answer(&lines)?;
                Ok(None)
            }
            (SdesRole::Answerer, SdesState::Initial) => {
                // Create answer
                let answer = self.create_answer(&lines)?;
                let answer_str = answer.join("\r\n");
                Ok(Some(answer_str.into_bytes()))
            }
            _ => Err(Error::InvalidState(
                "Invalid state for message processing".into(),
            )),
        }
    }

    fn get_srtp_key(&self) -> Option<SrtpCryptoKey> {
        self.srtp_key.clone()
    }

    fn get_srtp_suite(&self) -> Option<SrtpCryptoSuite> {
        self.srtp_suite.clone()
    }

    fn is_complete(&self) -> bool {
        self.state == SdesState::Completed
    }
}

#[cfg(test)]
mod tests;