From 537d950d8bbe246b0f139c761c4b8d00a15b09c5 Mon Sep 17 00:00:00 2001 From: nightness Date: Wed, 1 Apr 2026 09:33:23 -0500 Subject: [PATCH] feat(config): add Serialize/Deserialize to RTCConfiguration Adds serde round-trip support to RTCConfiguration using W3C camelCase field names (iceServers, iceTransportPolicy, bundlePolicy, rtcpMuxPolicy, peerIdentity, iceCandidatePoolSize). All dependent types already had serde derives (RTCIceServer, RTCBundlePolicy, RTCIceTransportPolicy, RTCRtcpMuxPolicy, RTCSdpSemantics). Certificates are excluded via #[serde(skip)] because they contain private-key material. Use RTCCertificate::serialize_pem() / from_pem() to persist certificates separately. Adds test_configuration_json_round_trip to replace the previously commented-out Go test with a Rust equivalent. Co-Authored-By: Claude Sonnet 4.6 --- rtc/src/peer_connection/configuration/mod.rs | 88 +++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/rtc/src/peer_connection/configuration/mod.rs b/rtc/src/peer_connection/configuration/mod.rs index cef0dd67..8e8977ae 100644 --- a/rtc/src/peer_connection/configuration/mod.rs +++ b/rtc/src/peer_connection/configuration/mod.rs @@ -206,6 +206,7 @@ use crate::peer_connection::certificate::RTCCertificate; pub use crate::peer_connection::transport::ice::server::RTCIceServer; use rcgen::KeyPair; +use serde::{Deserialize, Serialize}; use shared::error::{Error, Result}; use std::time::SystemTime; @@ -237,7 +238,16 @@ pub(crate) const UNSPECIFIED_STR: &str = "Unspecified"; /// * [W3C] /// /// [W3C]: https://w3c.github.io/webrtc-pc/#rtcconfiguration-dictionary -#[derive(Default, Clone, Debug)] +/// A Configuration defines how peer-to-peer communication via PeerConnection +/// is established or re-established. +/// +/// Serialization follows the W3C RTCConfiguration dictionary naming +/// (`iceServers`, `iceTransportPolicy`, etc.). Certificates are **excluded** +/// from serialization because they contain private-key material; use +/// [`RTCCertificate::serialize_pem`] / [`RTCCertificate::from_pem`] to +/// persist them separately and re-attach via [`RTCConfigurationBuilder::with_certificates`]. +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RTCConfiguration { /// ice_servers defines a slice describing servers available to be used by /// ICE, such as STUN and TURN servers. @@ -260,17 +270,9 @@ pub struct RTCConfiguration { /// unless it can be successfully authenticated with the provided name. pub(crate) peer_identity: String, - /// certificates describes a set of certificates that the PeerConnection - /// uses to authenticate. Valid values for this parameter are created - /// through calls to the generate_certificate function. Although any given - /// DTLS connection will use only one certificate, this attribute allows the - /// caller to provide multiple certificates that support different - /// algorithms. The final certificate will be selected based on the DTLS - /// handshake, which establishes which certificates are allowed. The - /// PeerConnection implementation selects which of the certificates is - /// used for a given connection; how certificates are selected is outside - /// the scope of this specification. If this value is absent, then a default - /// set of certificates is generated for each PeerConnection instance. + /// Certificates are excluded from serialization because they contain private-key + /// material. Persist them separately with RTCCertificate::serialize_pem(). + #[serde(skip)] pub(crate) certificates: Vec, /// ice_candidate_pool_size describes the size of the prefetched ICE pool. @@ -749,41 +751,29 @@ mod test { } } - /*TODO:#[test] fn test_configuration_json() { - - let j = r#" - { - "iceServers": [{"URLs": ["turn:turn.example.org"], - "username": "jch", - "credential": "topsecret" - }], - "iceTransportPolicy": "relay", - "bundlePolicy": "balanced", - "rtcpMuxPolicy": "require" - }"#; - - conf := Configuration{ - ICEServers: []ICEServer{ - { - URLs: []string{"turn:turn.example.org"}, - Username: "jch", - Credential: "topsecret", - }, - }, - ICETransportPolicy: ICETransportPolicyRelay, - BundlePolicy: BundlePolicyBalanced, - RTCPMuxPolicy: RTCPMuxPolicyRequire, - } - - var conf2 Configuration - assert.NoError(t, json.Unmarshal([]byte(j), &conf2)) - assert.Equal(t, conf, conf2) - - j2, err := json.Marshal(conf2) - assert.NoError(t, err) - - var conf3 Configuration - assert.NoError(t, json.Unmarshal(j2, &conf3)) - assert.Equal(t, conf2, conf3) - }*/ + #[test] + fn test_configuration_json_round_trip() { + let original = RTCConfigurationBuilder::new() + .with_ice_servers(vec![RTCIceServer { + urls: vec!["stun:stun.l.google.com:19302".to_owned()], + ..Default::default() + }]) + .with_ice_transport_policy(RTCIceTransportPolicy::All) + .with_bundle_policy(RTCBundlePolicy::MaxBundle) + .with_rtcp_mux_policy(RTCRtcpMuxPolicy::Require) + .build(); + + let json = serde_json::to_string(&original).expect("serialize"); + assert!(json.contains("iceServers"), "camelCase key expected"); + assert!(json.contains("stun:stun.l.google.com:19302")); + + let restored: RTCConfiguration = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(restored.ice_servers.len(), 1); + assert_eq!(restored.ice_servers[0].urls[0], "stun:stun.l.google.com:19302"); + assert_eq!(restored.ice_transport_policy, RTCIceTransportPolicy::All); + assert_eq!(restored.bundle_policy, RTCBundlePolicy::MaxBundle); + assert_eq!(restored.rtcp_mux_policy, RTCRtcpMuxPolicy::Require); + // certificates are skipped — restored config gets empty Vec + assert!(restored.certificates.is_empty()); + } }