// Copyright (c) 2019 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::net::IpAddr; use xso::{AsXml, FromXml}; use crate::jingle_dtls_srtp::Fingerprint; use crate::ns; /// Wrapper element for an ICE-UDP transport. #[derive(FromXml, AsXml, Debug, PartialEq, Clone, Default)] #[xml(namespace = ns::JINGLE_ICE_UDP, name = "transport")] pub struct Transport { /// A Password as defined in ICE-CORE. #[xml(attribute(default))] pwd: Option, /// A User Fragment as defined in ICE-CORE. #[xml(attribute(default))] ufrag: Option, /// List of candidates for this ICE-UDP session. #[xml(child(n = ..))] candidates: Vec, /// Fingerprint of the key used for the DTLS handshake. #[xml(child(default))] fingerprint: Option, } impl Transport { /// Create a new ICE-UDP transport. pub fn new() -> Transport { Transport::default() } /// Add a candidate to this transport. pub fn add_candidate(mut self, candidate: Candidate) -> Self { self.candidates.push(candidate); self } /// Set the DTLS-SRTP fingerprint of this transport. pub fn with_fingerprint(mut self, fingerprint: Fingerprint) -> Self { self.fingerprint = Some(fingerprint); self } } generate_attribute!( /// A Candidate Type as defined in ICE-CORE. Type, "type", { /// Host candidate. Host => "host", /// Peer reflexive candidate. Prflx => "prflx", /// Relayed candidate. Relay => "relay", /// Server reflexive candidate. Srflx => "srflx", } ); /// A candidate for an ICE-UDP session. #[derive(FromXml, AsXml, Debug, PartialEq, Clone)] #[xml(namespace = ns::JINGLE_ICE_UDP, name = "candidate")] pub struct Candidate { /// A Component ID as defined in ICE-CORE. #[xml(attribute)] pub component: u8, /// A Foundation as defined in ICE-CORE. #[xml(attribute)] pub foundation: String, /// An index, starting at 0, that enables the parties to keep track of updates to the /// candidate throughout the life of the session. #[xml(attribute)] pub generation: u8, /// A unique identifier for the candidate. #[xml(attribute)] pub id: String, /// The Internet Protocol (IP) address for the candidate transport mechanism; this can be /// either an IPv4 address or an IPv6 address. #[xml(attribute)] pub ip: IpAddr, /// The port at the candidate IP address. #[xml(attribute)] pub port: u16, /// A Priority as defined in ICE-CORE. #[xml(attribute)] pub priority: u32, /// The protocol to be used. The only value defined by this specification is "udp". #[xml(attribute)] pub protocol: String, /// A related address as defined in ICE-CORE. #[xml(attribute(default, name = "rel-addr"))] pub rel_addr: Option, /// A related port as defined in ICE-CORE. #[xml(attribute(default, name = "rel-port"))] pub rel_port: Option, /// An index, starting at 0, referencing which network this candidate is on for a given /// peer. #[xml(attribute(default))] pub network: Option, /// A Candidate Type as defined in ICE-CORE. #[xml(attribute(name = "type"))] pub type_: Type, } #[cfg(test)] mod tests { use super::*; use crate::hashes::Algo; use crate::jingle_dtls_srtp::Setup; use minidom::Element; #[cfg(target_pointer_width = "32")] #[test] fn test_size() { assert_size!(Transport, 64); assert_size!(Type, 1); assert_size!(Candidate, 88); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { assert_size!(Transport, 128); assert_size!(Type, 1); assert_size!(Candidate, 128); } #[test] fn test_gajim() { let elem: Element = " " .parse() .unwrap(); let transport = Transport::try_from(elem).unwrap(); assert_eq!(transport.pwd.unwrap(), "wakMJ8Ydd5rqnPaFerws5o"); assert_eq!(transport.ufrag.unwrap(), "aeXX"); } #[test] fn test_jitsi_meet() { let elem: Element = " 97:F2:B5:BE:DB:A6:00:B1:3E:40:B2:41:3C:0D:FC:E0:BD:B2:A0:E8 " .parse() .unwrap(); let transport = Transport::try_from(elem).unwrap(); assert_eq!(transport.pwd.unwrap(), "7lk9uul39gckit6t02oavv2r9j"); assert_eq!(transport.ufrag.unwrap(), "2acq51d4p07v2m"); let fingerprint = transport.fingerprint.unwrap(); assert_eq!(fingerprint.hash, Algo::Sha_1); assert_eq!(fingerprint.setup, Setup::Actpass); assert_eq!( fingerprint.value, [ 151, 242, 181, 190, 219, 166, 0, 177, 62, 64, 178, 65, 60, 13, 252, 224, 189, 178, 160, 232 ] ); } #[test] fn test_serialize_transport() { let reference: Element = "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" .parse() .unwrap(); let elem: Element = "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" .parse() .unwrap(); let fingerprint = Fingerprint::try_from(elem).unwrap(); let transport = Transport { pwd: None, ufrag: None, candidates: vec![], fingerprint: Some(fingerprint), }; let serialized: Element = transport.into(); assert_eq!(serialized, reference); } }