This commit is contained in:
Jasper Hugo 2022-03-06 16:24:02 +07:00
parent c68a7cbe0a
commit 44208e28b3
15 changed files with 581 additions and 499 deletions

View File

@ -53,7 +53,10 @@ struct Opt {
#[structopt(short, long, parse(from_occurrences))] #[structopt(short, long, parse(from_occurrences))]
verbose: u8, verbose: u8,
#[cfg(feature = "tls-insecure")] #[cfg(feature = "tls-insecure")]
#[structopt(long, help = "Disable TLS certificate verification (use with extreme caution)")] #[structopt(
long,
help = "Disable TLS certificate verification (use with extreme caution)"
)]
tls_insecure: bool, tls_insecure: bool,
} }

View File

@ -4,23 +4,23 @@ use xmpp_parsers::Error;
pub struct ColonSeparatedHex; pub struct ColonSeparatedHex;
impl ColonSeparatedHex { impl ColonSeparatedHex {
pub fn decode(s: &str) -> Result<Vec<u8>, Error> { pub fn decode(s: &str) -> Result<Vec<u8>, Error> {
let mut bytes = vec![]; let mut bytes = vec![];
for i in 0..(1 + s.len()) / 3 { for i in 0..(1 + s.len()) / 3 {
let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?; let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
if 3 * i + 2 < s.len() { if 3 * i + 2 < s.len() {
assert_eq!(&s[3 * i + 2..3 * i + 3], ":"); assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
} }
bytes.push(byte); bytes.push(byte);
}
Ok(bytes)
} }
Ok(bytes)
}
pub fn encode(b: &[u8]) -> Option<String> { pub fn encode(b: &[u8]) -> Option<String> {
let mut bytes = vec![]; let mut bytes = vec![];
for byte in b { for byte in b {
bytes.push(format!("{:02X}", byte)); bytes.push(format!("{:02X}", byte));
}
Some(bytes.join(":"))
} }
Some(bytes.join(":"))
}
} }

View File

@ -4,17 +4,15 @@ use jid::Jid;
use xmpp_parsers::{ use xmpp_parsers::{
iq::IqSetPayload, iq::IqSetPayload,
jingle::{ContentId, Creator, Disposition, ReasonElement, Senders, SessionId}, jingle::{ContentId, Creator, Disposition, ReasonElement, Senders, SessionId},
jingle_ibb::Transport as IbbTransport,
jingle_grouping::Group, jingle_grouping::Group,
jingle_ibb::Transport as IbbTransport,
jingle_s5b::Transport as Socks5Transport, jingle_s5b::Transport as Socks5Transport,
ns::{JINGLE, JINGLE_GROUPING, JINGLE_IBB, JINGLE_ICE_UDP, JINGLE_RTP, JINGLE_S5B}, ns::{JINGLE, JINGLE_GROUPING, JINGLE_IBB, JINGLE_ICE_UDP, JINGLE_RTP, JINGLE_S5B},
Element, Element, Error,
Error,
}; };
use crate::{ use crate::{
jingle_ice_udp::Transport as IceUdpTransport, jingle_ice_udp::Transport as IceUdpTransport, jingle_rtp::Description as RtpDescription,
jingle_rtp::Description as RtpDescription,
}; };
generate_attribute!( generate_attribute!(
@ -75,235 +73,242 @@ generate_attribute!(
/// The main Jingle container, to be included in an iq stanza. /// The main Jingle container, to be included in an iq stanza.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Jingle { pub struct Jingle {
/// The action to execute on both ends. /// The action to execute on both ends.
pub action: Action, pub action: Action,
/// Who the initiator is. /// Who the initiator is.
pub initiator: Option<Jid>, pub initiator: Option<Jid>,
/// Who the responder is. /// Who the responder is.
pub responder: Option<Jid>, pub responder: Option<Jid>,
/// Unique session identifier between two entities. /// Unique session identifier between two entities.
pub sid: SessionId, pub sid: SessionId,
/// A list of contents to be negotiated in this session. /// A list of contents to be negotiated in this session.
pub contents: Vec<Content>, pub contents: Vec<Content>,
/// An optional reason. /// An optional reason.
pub reason: Option<ReasonElement>, pub reason: Option<ReasonElement>,
/// An optional grouping. /// An optional grouping.
pub group: Option<Group>, pub group: Option<Group>,
/// Payloads to be included. /// Payloads to be included.
pub other: Vec<Element>, pub other: Vec<Element>,
} }
impl IqSetPayload for Jingle {} impl IqSetPayload for Jingle {}
impl Jingle { impl Jingle {
/// Create a new Jingle element. /// Create a new Jingle element.
pub fn new(action: Action, sid: SessionId) -> Jingle { pub fn new(action: Action, sid: SessionId) -> Jingle {
Jingle { Jingle {
action, action,
sid, sid,
initiator: None, initiator: None,
responder: None, responder: None,
contents: Vec::new(), contents: Vec::new(),
reason: None, reason: None,
group: None, group: None,
other: Vec::new(), other: Vec::new(),
}
} }
}
/// Set the initiators JID. /// Set the initiators JID.
pub fn with_initiator(mut self, initiator: Jid) -> Jingle { pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
self.initiator = Some(initiator); self.initiator = Some(initiator);
self self
} }
/// Set the responders JID. /// Set the responders JID.
pub fn with_responder(mut self, responder: Jid) -> Jingle { pub fn with_responder(mut self, responder: Jid) -> Jingle {
self.responder = Some(responder); self.responder = Some(responder);
self self
} }
/// Add a content to this Jingle container. /// Add a content to this Jingle container.
pub fn add_content(mut self, content: Content) -> Jingle { pub fn add_content(mut self, content: Content) -> Jingle {
self.contents.push(content); self.contents.push(content);
self self
} }
/// Set the reason in this Jingle container. /// Set the reason in this Jingle container.
pub fn set_reason(mut self, reason: ReasonElement) -> Jingle { pub fn set_reason(mut self, reason: ReasonElement) -> Jingle {
self.reason = Some(reason); self.reason = Some(reason);
self self
} }
/// Set the grouping in this Jingle container. /// Set the grouping in this Jingle container.
pub fn set_group(mut self, group: Group) -> Jingle { pub fn set_group(mut self, group: Group) -> Jingle {
self.group = Some(group); self.group = Some(group);
self self
} }
} }
impl TryFrom<Element> for Jingle { impl TryFrom<Element> for Jingle {
type Error = Error; type Error = Error;
fn try_from(root: Element) -> Result<Jingle, Error> { fn try_from(root: Element) -> Result<Jingle, Error> {
check_self!(root, "jingle", JINGLE, "Jingle"); check_self!(root, "jingle", JINGLE, "Jingle");
let mut jingle = Jingle { let mut jingle = Jingle {
action: get_attr!(root, "action", Required), action: get_attr!(root, "action", Required),
initiator: get_attr!(root, "initiator", Option), initiator: get_attr!(root, "initiator", Option),
responder: get_attr!(root, "responder", Option), responder: get_attr!(root, "responder", Option),
sid: get_attr!(root, "sid", Required), sid: get_attr!(root, "sid", Required),
contents: vec![], contents: vec![],
reason: None, reason: None,
group: None, group: None,
other: vec![], other: vec![],
}; };
for child in root.children().cloned() { for child in root.children().cloned() {
if child.is("content", JINGLE) { if child.is("content", JINGLE) {
let content = Content::try_from(child)?; let content = Content::try_from(child)?;
jingle.contents.push(content); jingle.contents.push(content);
} else if child.is("reason", JINGLE) { }
if jingle.reason.is_some() { else if child.is("reason", JINGLE) {
return Err(Error::ParseError( if jingle.reason.is_some() {
"Jingle must not have more than one reason.", return Err(Error::ParseError(
)); "Jingle must not have more than one reason.",
} ));
let reason = ReasonElement::try_from(child)?;
jingle.reason = Some(reason);
} else if child.is("group", JINGLE_GROUPING) {
if jingle.group.is_some() {
return Err(Error::ParseError(
"Jingle must not have more than one grouping.",
));
}
let group = Group::try_from(child)?;
jingle.group = Some(group);
} else {
jingle.other.push(child);
}
} }
let reason = ReasonElement::try_from(child)?;
Ok(jingle) jingle.reason = Some(reason);
}
else if child.is("group", JINGLE_GROUPING) {
if jingle.group.is_some() {
return Err(Error::ParseError(
"Jingle must not have more than one grouping.",
));
}
let group = Group::try_from(child)?;
jingle.group = Some(group);
}
else {
jingle.other.push(child);
}
} }
Ok(jingle)
}
} }
impl From<Jingle> for Element { impl From<Jingle> for Element {
fn from(jingle: Jingle) -> Element { fn from(jingle: Jingle) -> Element {
Element::builder("jingle", JINGLE) Element::builder("jingle", JINGLE)
.attr("action", jingle.action) .attr("action", jingle.action)
.attr("initiator", jingle.initiator) .attr("initiator", jingle.initiator)
.attr("responder", jingle.responder) .attr("responder", jingle.responder)
.attr("sid", jingle.sid) .attr("sid", jingle.sid)
.append_all(jingle.contents) .append_all(jingle.contents)
.append_all(jingle.reason.map(Element::from)) .append_all(jingle.reason.map(Element::from))
.append_all(jingle.group.map(Element::from)) .append_all(jingle.group.map(Element::from))
.build() .build()
} }
} }
/// Enum wrapping all of the various supported descriptions of a Content. /// Enum wrapping all of the various supported descriptions of a Content.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Description { pub enum Description {
/// Jingle RTP Sessions (XEP-0167) description. /// Jingle RTP Sessions (XEP-0167) description.
Rtp(RtpDescription), Rtp(RtpDescription),
/// To be used for any description that isnt known at compile-time. /// To be used for any description that isnt known at compile-time.
Unknown(Element), Unknown(Element),
} }
impl TryFrom<Element> for Description { impl TryFrom<Element> for Description {
type Error = Error; type Error = Error;
fn try_from(elem: Element) -> Result<Description, Error> { fn try_from(elem: Element) -> Result<Description, Error> {
Ok(if elem.is("description", JINGLE_RTP) { Ok(if elem.is("description", JINGLE_RTP) {
Description::Rtp(RtpDescription::try_from(elem)?) Description::Rtp(RtpDescription::try_from(elem)?)
} else {
Description::Unknown(elem)
})
} }
else {
Description::Unknown(elem)
})
}
} }
impl From<RtpDescription> for Description { impl From<RtpDescription> for Description {
fn from(desc: RtpDescription) -> Description { fn from(desc: RtpDescription) -> Description {
Description::Rtp(desc) Description::Rtp(desc)
} }
} }
impl From<Description> for Element { impl From<Description> for Element {
fn from(desc: Description) -> Element { fn from(desc: Description) -> Element {
match desc { match desc {
Description::Rtp(desc) => desc.into(), Description::Rtp(desc) => desc.into(),
Description::Unknown(elem) => elem, Description::Unknown(elem) => elem,
}
} }
}
} }
/// Enum wrapping all of the various supported transports of a Content. /// Enum wrapping all of the various supported transports of a Content.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Transport { pub enum Transport {
/// Jingle ICE-UDP Bytestreams (XEP-0176) transport. /// Jingle ICE-UDP Bytestreams (XEP-0176) transport.
IceUdp(IceUdpTransport), IceUdp(IceUdpTransport),
/// Jingle In-Band Bytestreams (XEP-0261) transport. /// Jingle In-Band Bytestreams (XEP-0261) transport.
Ibb(IbbTransport), Ibb(IbbTransport),
/// Jingle SOCKS5 Bytestreams (XEP-0260) transport. /// Jingle SOCKS5 Bytestreams (XEP-0260) transport.
Socks5(Socks5Transport), Socks5(Socks5Transport),
/// To be used for any transport that isnt known at compile-time. /// To be used for any transport that isnt known at compile-time.
Unknown(Element), Unknown(Element),
} }
impl TryFrom<Element> for Transport { impl TryFrom<Element> for Transport {
type Error = Error; type Error = Error;
fn try_from(elem: Element) -> Result<Transport, Error> { fn try_from(elem: Element) -> Result<Transport, Error> {
Ok(if elem.is("transport", JINGLE_ICE_UDP) { Ok(if elem.is("transport", JINGLE_ICE_UDP) {
Transport::IceUdp(IceUdpTransport::try_from(elem)?) Transport::IceUdp(IceUdpTransport::try_from(elem)?)
} else if elem.is("transport", JINGLE_IBB) {
Transport::Ibb(IbbTransport::try_from(elem)?)
} else if elem.is("transport", JINGLE_S5B) {
Transport::Socks5(Socks5Transport::try_from(elem)?)
} else {
Transport::Unknown(elem)
})
} }
else if elem.is("transport", JINGLE_IBB) {
Transport::Ibb(IbbTransport::try_from(elem)?)
}
else if elem.is("transport", JINGLE_S5B) {
Transport::Socks5(Socks5Transport::try_from(elem)?)
}
else {
Transport::Unknown(elem)
})
}
} }
impl From<IceUdpTransport> for Transport { impl From<IceUdpTransport> for Transport {
fn from(transport: IceUdpTransport) -> Transport { fn from(transport: IceUdpTransport) -> Transport {
Transport::IceUdp(transport) Transport::IceUdp(transport)
} }
} }
impl From<IbbTransport> for Transport { impl From<IbbTransport> for Transport {
fn from(transport: IbbTransport) -> Transport { fn from(transport: IbbTransport) -> Transport {
Transport::Ibb(transport) Transport::Ibb(transport)
} }
} }
impl From<Socks5Transport> for Transport { impl From<Socks5Transport> for Transport {
fn from(transport: Socks5Transport) -> Transport { fn from(transport: Socks5Transport) -> Transport {
Transport::Socks5(transport) Transport::Socks5(transport)
} }
} }
impl From<Transport> for Element { impl From<Transport> for Element {
fn from(transport: Transport) -> Element { fn from(transport: Transport) -> Element {
match transport { match transport {
Transport::IceUdp(transport) => transport.into(), Transport::IceUdp(transport) => transport.into(),
Transport::Ibb(transport) => transport.into(), Transport::Ibb(transport) => transport.into(),
Transport::Socks5(transport) => transport.into(), Transport::Socks5(transport) => transport.into(),
Transport::Unknown(elem) => elem, Transport::Unknown(elem) => elem,
}
} }
}
} }
generate_element!( generate_element!(
@ -338,44 +343,44 @@ generate_element!(
impl Content { impl Content {
/// Create a new content. /// Create a new content.
pub fn new(creator: Creator, name: ContentId) -> Content { pub fn new(creator: Creator, name: ContentId) -> Content {
Content { Content {
creator: Some(creator), creator: Some(creator),
name, name,
disposition: Disposition::Session, disposition: Disposition::Session,
senders: Senders::Both, senders: Senders::Both,
description: None, description: None,
transport: None, transport: None,
security: None, security: None,
} }
} }
/// Set how the content is to be interpreted by the recipient. /// Set how the content is to be interpreted by the recipient.
pub fn with_disposition(mut self, disposition: Disposition) -> Content { pub fn with_disposition(mut self, disposition: Disposition) -> Content {
self.disposition = disposition; self.disposition = disposition;
self self
} }
/// Specify who can send data for this content. /// Specify who can send data for this content.
pub fn with_senders(mut self, senders: Senders) -> Content { pub fn with_senders(mut self, senders: Senders) -> Content {
self.senders = senders; self.senders = senders;
self self
} }
/// Set the description of this content. /// Set the description of this content.
pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content { pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content {
self.description = Some(description.into()); self.description = Some(description.into());
self self
} }
/// Set the transport of this content. /// Set the transport of this content.
pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content { pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content {
self.transport = Some(transport.into()); self.transport = Some(transport.into());
self self
} }
/// Set the security of this content. /// Set the security of this content.
pub fn with_security(mut self, security: Element) -> Content { pub fn with_security(mut self, security: Element) -> Content {
self.security = Some(security); self.security = Some(security);
self self
} }
} }

View File

@ -26,22 +26,21 @@ generate_element!(
impl Fingerprint { impl Fingerprint {
/// Create a new Fingerprint from a Setup and a Hash. /// Create a new Fingerprint from a Setup and a Hash.
pub fn from_hash(setup: Setup, hash: Hash) -> Fingerprint { pub fn from_hash(setup: Setup, hash: Hash) -> Fingerprint {
Fingerprint { Fingerprint {
hash: hash.algo, hash: hash.algo,
setup: Some(setup), setup: Some(setup),
value: hash.hash, value: hash.hash,
} }
} }
/// Create a new Fingerprint from a Setup and parsing the hash. /// Create a new Fingerprint from a Setup and parsing the hash.
pub fn from_colon_separated_hex( pub fn from_colon_separated_hex(
setup: Setup, setup: Setup,
algo: &str, algo: &str,
hash: &str, hash: &str,
) -> Result<Fingerprint, Error> { ) -> Result<Fingerprint, Error> {
let algo = algo.parse()?; let algo = algo.parse()?;
let hash = Hash::from_colon_separated_hex(algo, hash)?; let hash = Hash::from_colon_separated_hex(algo, hash)?;
Ok(Fingerprint::from_hash(setup, hash)) Ok(Fingerprint::from_hash(setup, hash))
} }
} }

View File

@ -3,10 +3,7 @@ use xmpp_parsers::{
ns::{JINGLE_DTLS, JINGLE_ICE_UDP}, ns::{JINGLE_DTLS, JINGLE_ICE_UDP},
}; };
use crate::{ use crate::{jingle_dtls_srtp::Fingerprint, ns::JITSI_COLIBRI};
jingle_dtls_srtp::Fingerprint,
ns::JITSI_COLIBRI,
};
generate_element!( generate_element!(
/// Wrapper element for an ICE-UDP transport. /// Wrapper element for an ICE-UDP transport.

View File

@ -43,14 +43,14 @@ generate_element!(
impl Description { impl Description {
/// Create a new RTP description. /// Create a new RTP description.
pub fn new(media: String) -> Description { pub fn new(media: String) -> Description {
Description { Description {
media, media,
ssrc: None, ssrc: None,
payload_types: Vec::new(), payload_types: Vec::new(),
rtcp_mux: None, rtcp_mux: None,
ssrc_groups: Vec::new(), ssrc_groups: Vec::new(),
ssrcs: Vec::new(), ssrcs: Vec::new(),
hdrexts: Vec::new(), hdrexts: Vec::new(),
} }
} }
} }

View File

@ -1,4 +1,7 @@
use xmpp_parsers::{jingle_ssma::{Parameter, Semantics}, ns::JINGLE_SSMA}; use xmpp_parsers::{
jingle_ssma::{Parameter, Semantics},
ns::JINGLE_SSMA,
};
use crate::ns::JITSI_MEET; use crate::ns::JITSI_MEET;

View File

@ -8,57 +8,57 @@
macro_rules! get_attr { macro_rules! get_attr {
($elem:ident, $attr:tt, $type:tt) => { ($elem:ident, $attr:tt, $type:tt) => {
get_attr!($elem, $attr, $type, value, value.parse()?) get_attr!($elem, $attr, $type, value, value.parse()?)
}; };
($elem:ident, $attr:tt, OptionEmpty, $value:ident, $func:expr) => { ($elem:ident, $attr:tt, OptionEmpty, $value:ident, $func:expr) => {
match $elem.attr($attr) { match $elem.attr($attr) {
Some("") => None, Some("") => None,
Some($value) => Some($func), Some($value) => Some($func),
None => None, None => None,
} }
}; };
($elem:ident, $attr:tt, Option, $value:ident, $func:expr) => { ($elem:ident, $attr:tt, Option, $value:ident, $func:expr) => {
match $elem.attr($attr) { match $elem.attr($attr) {
Some($value) => Some($func), Some($value) => Some($func),
None => None, None => None,
} }
}; };
($elem:ident, $attr:tt, Required, $value:ident, $func:expr) => { ($elem:ident, $attr:tt, Required, $value:ident, $func:expr) => {
match $elem.attr($attr) { match $elem.attr($attr) {
Some($value) => $func, Some($value) => $func,
None => { None => {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Required attribute '", "Required attribute '",
$attr, $attr,
"' missing." "' missing."
))); )));
} },
} }
}; };
($elem:ident, $attr:tt, RequiredNonEmpty, $value:ident, $func:expr) => { ($elem:ident, $attr:tt, RequiredNonEmpty, $value:ident, $func:expr) => {
match $elem.attr($attr) { match $elem.attr($attr) {
Some("") => { Some("") => {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Required attribute '", "Required attribute '",
$attr, $attr,
"' must not be empty." "' must not be empty."
))); )));
} },
Some($value) => $func, Some($value) => $func,
None => { None => {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Required attribute '", "Required attribute '",
$attr, $attr,
"' missing." "' missing."
))); )));
} },
} }
}; };
($elem:ident, $attr:tt, Default, $value:ident, $func:expr) => { ($elem:ident, $attr:tt, Default, $value:ident, $func:expr) => {
match $elem.attr($attr) { match $elem.attr($attr) {
Some($value) => $func, Some($value) => $func,
None => ::std::default::Default::default(), None => ::std::default::Default::default(),
} }
}; };
} }
@ -300,28 +300,28 @@ macro_rules! generate_attribute_enum {
macro_rules! check_self { macro_rules! check_self {
($elem:ident, $name:tt, $ns:ident) => { ($elem:ident, $name:tt, $ns:ident) => {
check_self!($elem, $name, $ns, $name); check_self!($elem, $name, $ns, $name);
}; };
($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => { ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
if !$elem.is($name, $ns) { if !$elem.is($name, $ns) {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"This is not a ", "This is not a ",
$pretty_name, $pretty_name,
" element." " element."
))); )));
} }
}; };
} }
macro_rules! check_ns_only { macro_rules! check_ns_only {
($elem:ident, $name:tt, $ns:ident) => { ($elem:ident, $name:tt, $ns:ident) => {
if !$elem.has_ns($ns) { if !$elem.has_ns($ns) {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"This is not a ", "This is not a ",
$name, $name,
" element." " element."
))); )));
} }
}; };
} }
@ -437,141 +437,142 @@ macro_rules! start_decl {
macro_rules! start_parse_elem { macro_rules! start_parse_elem {
($temp:ident: Vec) => { ($temp:ident: Vec) => {
let mut $temp = Vec::new(); let mut $temp = Vec::new();
}; };
($temp:ident: Option) => { ($temp:ident: Option) => {
let mut $temp = None; let mut $temp = None;
}; };
($temp:ident: Required) => { ($temp:ident: Required) => {
let mut $temp = None; let mut $temp = None;
}; };
($temp:ident: Present) => { ($temp:ident: Present) => {
let mut $temp = false; let mut $temp = false;
}; };
} }
macro_rules! do_parse { macro_rules! do_parse {
($elem:ident, Element) => { ($elem:ident, Element) => {
$elem.clone() $elem.clone()
}; };
($elem:ident, String) => { ($elem:ident, String) => {
$elem.text() $elem.text()
}; };
($elem:ident, $constructor:ident) => { ($elem:ident, $constructor:ident) => {
$constructor::try_from($elem.clone())? $constructor::try_from($elem.clone())?
}; };
} }
macro_rules! do_parse_elem { macro_rules! do_parse_elem {
($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
$temp.push(do_parse!($elem, $constructor)); $temp.push(do_parse!($elem, $constructor));
}; };
($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
if $temp.is_some() { if $temp.is_some() {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Element ", "Element ",
$parent_name, $parent_name,
" must not have more than one ", " must not have more than one ",
$name, $name,
" child." " child."
))); )));
} }
$temp = Some(do_parse!($elem, $constructor)); $temp = Some(do_parse!($elem, $constructor));
}; };
($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
if $temp.is_some() { if $temp.is_some() {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Element ", "Element ",
$parent_name, $parent_name,
" must not have more than one ", " must not have more than one ",
$name, $name,
" child." " child."
))); )));
} }
$temp = Some(do_parse!($elem, $constructor)); $temp = Some(do_parse!($elem, $constructor));
}; };
($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => { ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
if $temp { if $temp {
return Err(xmpp_parsers::Error::ParseError(concat!( return Err(xmpp_parsers::Error::ParseError(concat!(
"Element ", "Element ",
$parent_name, $parent_name,
" must not have more than one ", " must not have more than one ",
$name, $name,
" child." " child."
))); )));
} }
$temp = true; $temp = true;
}; };
} }
macro_rules! finish_parse_elem { macro_rules! finish_parse_elem {
($temp:ident: Vec = $name:tt, $parent_name:tt) => { ($temp:ident: Vec = $name:tt, $parent_name:tt) => {
$temp $temp
}; };
($temp:ident: Option = $name:tt, $parent_name:tt) => { ($temp:ident: Option = $name:tt, $parent_name:tt) => {
$temp $temp
}; };
($temp:ident: Required = $name:tt, $parent_name:tt) => { ($temp:ident: Required = $name:tt, $parent_name:tt) => {
$temp.ok_or(xmpp_parsers::Error::ParseError(concat!( $temp.ok_or(xmpp_parsers::Error::ParseError(concat!(
"Missing child ", "Missing child ",
$name, $name,
" in ", " in ",
$parent_name, $parent_name,
" element." " element."
)))? )))?
}; };
($temp:ident: Present = $name:tt, $parent_name:tt) => { ($temp:ident: Present = $name:tt, $parent_name:tt) => {
$temp $temp
}; };
} }
macro_rules! generate_serialiser { macro_rules! generate_serialiser {
($builder:ident, $parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
$builder.append( $builder.append(
xmpp_parsers::Element::builder($name, $ns) xmpp_parsers::Element::builder($name, $ns).append(::minidom::Node::Text($parent.$elem)),
.append(::minidom::Node::Text($parent.$elem)), )
)
}; };
($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.map(|elem| { $builder.append_all(
xmpp_parsers::Element::builder($name, $ns).append(::minidom::Node::Text(elem)) $parent
})) .$elem
.map(|elem| xmpp_parsers::Element::builder($name, $ns).append(::minidom::Node::Text(elem))),
)
}; };
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => { ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, *)) => {
$builder.append_all( $builder.append_all(
$parent $parent
.$elem .$elem
.map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))), .map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))),
) )
}; };
($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, Option, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append_all( $builder.append_all(
$parent $parent
.$elem .$elem
.map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))), .map(|elem| ::minidom::Node::Element(xmpp_parsers::Element::from(elem))),
) )
}; };
($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.into_iter()) $builder.append_all($parent.$elem.into_iter())
}; };
($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append(::minidom::Node::Element( $builder.append(::minidom::Node::Element(
xmpp_parsers::Element::builder($name, $ns).build(), xmpp_parsers::Element::builder($name, $ns).build(),
)) ))
}; };
($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => { ($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append(::minidom::Node::Element(xmpp_parsers::Element::from( $builder.append(::minidom::Node::Element(xmpp_parsers::Element::from(
$parent.$elem, $parent.$elem,
))) )))
}; };
} }
macro_rules! generate_child_test { macro_rules! generate_child_test {
($child:ident, $name:tt, *) => { ($child:ident, $name:tt, *) => {
$child.is($name, ::minidom::NSChoice::Any) $child.is($name, ::minidom::NSChoice::Any)
}; };
($child:ident, $name:tt, $ns:tt) => { ($child:ident, $name:tt, $ns:tt) => {
$child.is($name, $ns) $child.is($name, $ns)
}; };
} }
@ -671,48 +672,48 @@ macro_rules! assert_size (
// TODO: move that to src/pubsub/mod.rs, once we figure out how to use macros from there. // TODO: move that to src/pubsub/mod.rs, once we figure out how to use macros from there.
macro_rules! impl_pubsub_item { macro_rules! impl_pubsub_item {
($item:ident, $ns:ident) => { ($item:ident, $ns:ident) => {
impl ::std::convert::TryFrom<xmpp_parsers::Element> for $item { impl ::std::convert::TryFrom<xmpp_parsers::Element> for $item {
type Error = Error; type Error = Error;
fn try_from(elem: xmpp_parsers::Element) -> Result<$item, Error> { fn try_from(elem: xmpp_parsers::Element) -> Result<$item, Error> {
check_self!(elem, "item", $ns); check_self!(elem, "item", $ns);
let mut payloads = elem.children().cloned().collect::<Vec<_>>(); let mut payloads = elem.children().cloned().collect::<Vec<_>>();
let payload = payloads.pop(); let payload = payloads.pop();
if !payloads.is_empty() { if !payloads.is_empty() {
return Err(Error::ParseError( return Err(Error::ParseError(
"More than a single payload in item element.", "More than a single payload in item element.",
)); ));
} }
Ok($item(xmpp_parsers::pubsub::Item { Ok($item(xmpp_parsers::pubsub::Item {
id: get_attr!(elem, "id", Option), id: get_attr!(elem, "id", Option),
publisher: get_attr!(elem, "publisher", Option), publisher: get_attr!(elem, "publisher", Option),
payload, payload,
})) }))
}
} }
}
impl From<$item> for xmpp_parsers::Element { impl From<$item> for xmpp_parsers::Element {
fn from(item: $item) -> xmpp_parsers::Element { fn from(item: $item) -> xmpp_parsers::Element {
xmpp_parsers::Element::builder("item", $ns) xmpp_parsers::Element::builder("item", $ns)
.attr("id", item.0.id) .attr("id", item.0.id)
.attr("publisher", item.0.publisher) .attr("publisher", item.0.publisher)
.append_all(item.0.payload) .append_all(item.0.payload)
.build() .build()
}
} }
}
impl ::std::ops::Deref for $item { impl ::std::ops::Deref for $item {
type Target = xmpp_parsers::pubsub::Item; type Target = xmpp_parsers::pubsub::Item;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
}
} }
}
impl ::std::ops::DerefMut for $item { impl ::std::ops::DerefMut for $item {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
}
} }
}
}; };
} }

View File

@ -139,10 +139,13 @@ pub unsafe extern "C" fn gstmeet_connection_join_conference(
}; };
let region = if (*config).region.is_null() { let region = if (*config).region.is_null() {
None None
} else { }
Some(CStr::from_ptr((*config).region) else {
.to_string_lossy() Some(
.to_string()) CStr::from_ptr((*config).region)
.to_string_lossy()
.to_string(),
)
}; };
let config = JitsiConferenceConfig { let config = JitsiConferenceConfig {
muc, muc,

View File

@ -6,7 +6,7 @@ use futures::{
sink::SinkExt, sink::SinkExt,
stream::{StreamExt, TryStreamExt}, stream::{StreamExt, TryStreamExt},
}; };
use rand::{RngCore, thread_rng}; use rand::{thread_rng, RngCore};
use tokio::{ use tokio::{
sync::{mpsc, Mutex}, sync::{mpsc, Mutex},
time::sleep, time::sleep,
@ -46,7 +46,13 @@ impl ColibriChannel {
.header("connection", "Upgrade") .header("connection", "Upgrade")
.header("upgrade", "websocket") .header("upgrade", "websocket")
.body(())?; .body(())?;
match tokio_tungstenite::connect_async_tls_with_config(request, None, Some(wss_connector(tls_insecure)?)).await { match tokio_tungstenite::connect_async_tls_with_config(
request,
None,
Some(wss_connector(tls_insecure)?),
)
.await
{
Ok((websocket, _)) => break websocket, Ok((websocket, _)) => break websocket,
Err(e) => { Err(e) => {
if retries < MAX_CONNECT_RETRIES { if retries < MAX_CONNECT_RETRIES {
@ -57,7 +63,7 @@ impl ColibriChannel {
else { else {
return Err(e).context("Failed to connect Colibri WebSocket"); return Err(e).context("Failed to connect Colibri WebSocket");
} }
} },
} }
}; };

View File

@ -15,13 +15,13 @@ use tracing::{debug, error, info, trace, warn};
use uuid::Uuid; use uuid::Uuid;
pub use xmpp_parsers::disco::Feature; pub use xmpp_parsers::disco::Feature;
use xmpp_parsers::{ use xmpp_parsers::{
disco::{DiscoInfoQuery, DiscoInfoResult, Identity},
caps::{self, Caps}, caps::{self, Caps},
disco::{DiscoInfoQuery, DiscoInfoResult, Identity},
ecaps2::{self, ECaps2}, ecaps2::{self, ECaps2},
hashes::{Algo, Hash}, hashes::{Algo, Hash},
iq::{Iq, IqType}, iq::{Iq, IqType},
message::{Message, MessageType}, message::{Message, MessageType},
muc::{Muc, MucUser, user::Status as MucStatus}, muc::{user::Status as MucStatus, Muc, MucUser},
nick::Nick, nick::Nick,
ns, ns,
presence::{self, Presence}, presence::{self, Presence},
@ -42,9 +42,7 @@ const DISCO_NODE: &str = "https://github.com/avstack/gst-meet";
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult { static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult {
node: None, node: None,
identities: vec![ identities: vec![Identity::new("client", "bot", "en", "gst-meet")],
Identity::new("client", "bot", "en", "gst-meet"),
],
features: vec![ features: vec![
Feature::new(ns::DISCO_INFO), Feature::new(ns::DISCO_INFO),
Feature::new(ns::JINGLE_RTP_AUDIO), Feature::new(ns::JINGLE_RTP_AUDIO),
@ -59,9 +57,8 @@ static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult {
extensions: vec![], extensions: vec![],
}); });
static COMPUTED_CAPS_HASH: Lazy<Hash> = Lazy::new(|| { static COMPUTED_CAPS_HASH: Lazy<Hash> =
caps::hash_caps(&caps::compute_disco(&DISCO_INFO), Algo::Sha_1).unwrap() Lazy::new(|| caps::hash_caps(&caps::compute_disco(&DISCO_INFO), Algo::Sha_1).unwrap());
});
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum JitsiConferenceState { enum JitsiConferenceState {
@ -154,18 +151,23 @@ impl JitsiConference {
let focus = config.focus.clone(); let focus = config.focus.clone();
let ecaps2_hash = let ecaps2_hash = ecaps2::hash_ecaps2(&ecaps2::compute_disco(&DISCO_INFO)?, Algo::Sha_256)?;
ecaps2::hash_ecaps2(&ecaps2::compute_disco(&DISCO_INFO)?, Algo::Sha_256)?;
let mut presence = vec![ let mut presence = vec![
Muc::new().into(), Muc::new().into(),
Caps::new(DISCO_NODE, COMPUTED_CAPS_HASH.clone()).into(), Caps::new(DISCO_NODE, COMPUTED_CAPS_HASH.clone()).into(),
ECaps2::new(vec![ecaps2_hash]).into(), ECaps2::new(vec![ecaps2_hash]).into(),
Element::builder("stats-id", ns::DEFAULT_NS).append("gst-meet").build(), Element::builder("stats-id", ns::DEFAULT_NS)
.append("gst-meet")
.build(),
Element::builder("jitsi_participant_codecType", ns::DEFAULT_NS) Element::builder("jitsi_participant_codecType", ns::DEFAULT_NS)
.append(config.video_codec.as_str()) .append(config.video_codec.as_str())
.build(), .build(),
Element::builder("audiomuted", ns::DEFAULT_NS).append("false").build(), Element::builder("audiomuted", ns::DEFAULT_NS)
Element::builder("videomuted", ns::DEFAULT_NS).append("false").build(), .append("false")
.build(),
Element::builder("videomuted", ns::DEFAULT_NS)
.append("false")
.build(),
Element::builder("nick", "http://jabber.org/protocol/nick") Element::builder("nick", "http://jabber.org/protocol/nick")
.append(config.nick.as_str()) .append(config.nick.as_str())
.build(), .build(),
@ -268,14 +270,17 @@ impl JitsiConference {
#[tracing::instrument(level = "debug", err)] #[tracing::instrument(level = "debug", err)]
pub async fn set_muted(&self, media_type: MediaType, muted: bool) -> Result<()> { pub async fn set_muted(&self, media_type: MediaType, muted: bool) -> Result<()> {
let mut locked_inner = self.inner.lock().await; let mut locked_inner = self.inner.lock().await;
let element = Element::builder(media_type.jitsi_muted_presence_element_name(), ns::DEFAULT_NS) let element = Element::builder(
.append(muted.to_string()) media_type.jitsi_muted_presence_element_name(),
.build(); ns::DEFAULT_NS,
locked_inner.presence.retain(|el| el.name() != media_type.jitsi_muted_presence_element_name()); )
.append(muted.to_string())
.build();
locked_inner
.presence
.retain(|el| el.name() != media_type.jitsi_muted_presence_element_name());
locked_inner.presence.push(element); locked_inner.presence.push(element);
self self.send_presence(&locked_inner.presence).await
.send_presence(&locked_inner.presence)
.await
} }
pub async fn pipeline(&self) -> Result<gstreamer::Pipeline> { pub async fn pipeline(&self) -> Result<gstreamer::Pipeline> {
@ -479,7 +484,11 @@ impl StanzaFilter for JitsiConference {
}, },
JoiningMuc => { JoiningMuc => {
let presence = Presence::try_from(element)?; let presence = Presence::try_from(element)?;
if let Some(payload) = presence.payloads.iter().find(|payload| payload.is("x", ns::MUC_USER)) { if let Some(payload) = presence
.payloads
.iter()
.find(|payload| payload.is("x", ns::MUC_USER))
{
let muc_user = MucUser::try_from(payload.clone())?; let muc_user = MucUser::try_from(payload.clone())?;
if muc_user.status.contains(&MucStatus::SelfPresence) { if muc_user.status.contains(&MucStatus::SelfPresence) {
debug!("Joined MUC: {}", self.config.muc); debug!("Joined MUC: {}", self.config.muc);
@ -500,23 +509,28 @@ impl StanzaFilter for JitsiConference {
if let Some(node) = query.node { if let Some(node) = query.node {
match node.splitn(2, '#').collect::<Vec<_>>().as_slice() { match node.splitn(2, '#').collect::<Vec<_>>().as_slice() {
// TODO: also support ecaps2, as we send it in our presence. // TODO: also support ecaps2, as we send it in our presence.
[uri, hash] if *uri == DISCO_NODE && *hash == COMPUTED_CAPS_HASH.to_base64() => { [uri, hash]
if *uri == DISCO_NODE && *hash == COMPUTED_CAPS_HASH.to_base64() =>
{
let mut disco_info = DISCO_INFO.clone(); let mut disco_info = DISCO_INFO.clone();
disco_info.node = Some(node); disco_info.node = Some(node);
let iq = Iq::from_result(iq.id, Some(disco_info)) let iq = Iq::from_result(iq.id, Some(disco_info))
.with_from(Jid::Full(self.jid.clone())) .with_from(Jid::Full(self.jid.clone()))
.with_to(iq.from.unwrap()); .with_to(iq.from.unwrap());
self.xmpp_tx.send(iq.into()).await?; self.xmpp_tx.send(iq.into()).await?;
} },
_ => { _ => {
let error = StanzaError::new( let error = StanzaError::new(
ErrorType::Cancel, DefinedCondition::ItemNotFound, ErrorType::Cancel,
"en", format!("Unknown disco#info node: {}", node)); DefinedCondition::ItemNotFound,
"en",
format!("Unknown disco#info node: {}", node),
);
let iq = Iq::from_error(iq.id, error) let iq = Iq::from_error(iq.id, error)
.with_from(Jid::Full(self.jid.clone())) .with_from(Jid::Full(self.jid.clone()))
.with_to(iq.from.unwrap()); .with_to(iq.from.unwrap());
self.xmpp_tx.send(iq.into()).await?; self.xmpp_tx.send(iq.into()).await?;
} },
} }
} }
else { else {
@ -579,7 +593,8 @@ impl StanzaFilter for JitsiConference {
if let Some(colibri_url) = colibri_url { if let Some(colibri_url) = colibri_url {
info!("Connecting Colibri WebSocket to {}", colibri_url); info!("Connecting Colibri WebSocket to {}", colibri_url);
let colibri_channel = ColibriChannel::new(&colibri_url, self.tls_insecure).await?; let colibri_channel =
ColibriChannel::new(&colibri_url, self.tls_insecure).await?;
let (tx, rx) = mpsc::channel(8); let (tx, rx) = mpsc::channel(8);
colibri_channel.subscribe(tx).await; colibri_channel.subscribe(tx).await;
jingle_session.colibri_channel = Some(colibri_channel); jingle_session.colibri_channel = Some(colibri_channel);
@ -592,8 +607,7 @@ impl StanzaFilter for JitsiConference {
// End-to-end ping // End-to-end ping
if let ColibriMessage::EndpointMessage { to, .. } = &msg { if let ColibriMessage::EndpointMessage { to, .. } = &msg {
// if to == // if to ==
} }
let locked_inner = self_.inner.lock().await; let locked_inner = self_.inner.lock().await;
@ -687,7 +701,9 @@ impl StanzaFilter for JitsiConference {
if let Err(e) = f(self.clone(), participant.clone()).await { if let Err(e) = f(self.clone(), participant.clone()).await {
warn!("on_participant failed: {:?}", e); warn!("on_participant failed: {:?}", e);
} }
else if let Some(jingle_session) = self.jingle_session.lock().await.as_ref() { else if let Some(jingle_session) =
self.jingle_session.lock().await.as_ref()
{
gstreamer::debug_bin_to_dot_file( gstreamer::debug_bin_to_dot_file(
&jingle_session.pipeline(), &jingle_session.pipeline(),
gstreamer::DebugGraphDetails::ALL, gstreamer::DebugGraphDetails::ALL,

View File

@ -72,7 +72,8 @@ impl Codec {
fn is_rtx(&self, rtx_pt: u8) -> bool { fn is_rtx(&self, rtx_pt: u8) -> bool {
if let Some(pt) = self.rtx_pt { if let Some(pt) = self.rtx_pt {
pt == rtx_pt pt == rtx_pt
} else { }
else {
false false
} }
} }
@ -123,10 +124,10 @@ impl Codec {
} }
struct ParsedRtpDescription { struct ParsedRtpDescription {
codecs: Vec<Codec>, codecs: Vec<Codec>,
audio_hdrext_ssrc_audio_level: Option<u16>, audio_hdrext_ssrc_audio_level: Option<u16>,
audio_hdrext_transport_cc: Option<u16>, audio_hdrext_transport_cc: Option<u16>,
video_hdrext_transport_cc: Option<u16>, video_hdrext_transport_cc: Option<u16>,
} }
pub(crate) struct JingleSession { pub(crate) struct JingleSession {
@ -180,7 +181,10 @@ impl JingleSession {
Ok(self.pipeline_state_null_rx.await?) Ok(self.pipeline_state_null_rx.await?)
} }
fn parse_rtp_description(description: &RtpDescription, remote_ssrc_map: &mut HashMap<u32, Source>) -> Result<Option<ParsedRtpDescription>> { fn parse_rtp_description(
description: &RtpDescription,
remote_ssrc_map: &mut HashMap<u32, Source>,
) -> Result<Option<ParsedRtpDescription>> {
let mut opus = None; let mut opus = None;
let mut h264 = None; let mut h264 = None;
let mut vp8 = None; let mut vp8 = None;
@ -222,7 +226,7 @@ impl JingleSession {
rtx_pt: None, rtx_pt: None,
rtcp_fbs: pt.rtcp_fbs.clone(), rtcp_fbs: pt.rtcp_fbs.clone(),
}); });
} },
"VP8" => { "VP8" => {
vp8 = Some(Codec { vp8 = Some(Codec {
name: CodecName::Vp8, name: CodecName::Vp8,
@ -230,7 +234,7 @@ impl JingleSession {
rtx_pt: None, rtx_pt: None,
rtcp_fbs: pt.rtcp_fbs.clone(), rtcp_fbs: pt.rtcp_fbs.clone(),
}); });
} },
"VP9" => { "VP9" => {
vp9 = Some(Codec { vp9 = Some(Codec {
name: CodecName::Vp9, name: CodecName::Vp9,
@ -238,7 +242,7 @@ impl JingleSession {
rtx_pt: None, rtx_pt: None,
rtcp_fbs: pt.rtcp_fbs.clone(), rtcp_fbs: pt.rtcp_fbs.clone(),
}); });
} },
_ => (), _ => (),
} }
} }
@ -319,14 +323,17 @@ impl JingleSession {
); );
} }
Ok(Some(ParsedRtpDescription { Ok(Some(ParsedRtpDescription {
codecs, codecs,
audio_hdrext_ssrc_audio_level, audio_hdrext_ssrc_audio_level,
audio_hdrext_transport_cc, audio_hdrext_transport_cc,
video_hdrext_transport_cc, video_hdrext_transport_cc,
})) }))
} }
async fn setup_ice(conference: &JitsiConference, transport: &IceUdpTransport) -> Result<(nice::Agent, u32, u32)> { async fn setup_ice(
conference: &JitsiConference,
transport: &IceUdpTransport,
) -> Result<(nice::Agent, u32, u32)> {
let ice_agent = nice::Agent::new(&conference.glib_main_context, nice::Compatibility::Rfc5245); let ice_agent = nice::Agent::new(&conference.glib_main_context, nice::Compatibility::Rfc5245);
ice_agent.set_ice_tcp(false); ice_agent.set_ice_tcp(false);
ice_agent.set_upnp(false); ice_agent.set_upnp(false);
@ -467,11 +474,16 @@ impl JingleSession {
for content in &jingle.contents { for content in &jingle.contents {
if let Some(Description::Rtp(description)) = &content.description { if let Some(Description::Rtp(description)) = &content.description {
if let Some(description) = JingleSession::parse_rtp_description(description, &mut remote_ssrc_map)? { if let Some(description) =
JingleSession::parse_rtp_description(description, &mut remote_ssrc_map)?
{
codecs.extend(description.codecs); codecs.extend(description.codecs);
audio_hdrext_ssrc_audio_level = audio_hdrext_ssrc_audio_level.or(description.audio_hdrext_ssrc_audio_level); audio_hdrext_ssrc_audio_level =
audio_hdrext_transport_cc = audio_hdrext_transport_cc.or(description.audio_hdrext_transport_cc); audio_hdrext_ssrc_audio_level.or(description.audio_hdrext_ssrc_audio_level);
video_hdrext_transport_cc = video_hdrext_transport_cc.or(description.video_hdrext_transport_cc); audio_hdrext_transport_cc =
audio_hdrext_transport_cc.or(description.audio_hdrext_transport_cc);
video_hdrext_transport_cc =
video_hdrext_transport_cc.or(description.video_hdrext_transport_cc);
} }
} }
@ -520,7 +532,8 @@ impl JingleSession {
debug!("video SSRC: {}", video_ssrc); debug!("video SSRC: {}", video_ssrc);
debug!("video RTX SSRC: {}", video_rtx_ssrc); debug!("video RTX SSRC: {}", video_rtx_ssrc);
let (ice_agent, ice_stream_id, ice_component_id) = JingleSession::setup_ice(conference, ice_transport).await?; let (ice_agent, ice_stream_id, ice_component_id) =
JingleSession::setup_ice(conference, ice_transport).await?;
let (ice_local_ufrag, ice_local_pwd) = ice_agent let (ice_local_ufrag, ice_local_pwd) = ice_agent
.local_credentials(ice_stream_id) .local_credentials(ice_stream_id)
@ -662,9 +675,14 @@ impl JingleSession {
None None
}); });
let pts: Vec<(String, u32)> = codecs.iter() let pts: Vec<(String, u32)> = codecs
.iter()
.filter(|codec| codec.is_video()) .filter(|codec| codec.is_video())
.flat_map(|codec| codec.rtx_pt.map(|rtx_pt| (codec.pt.to_string(), rtx_pt as u32))) .flat_map(|codec| {
codec
.rtx_pt
.map(|rtx_pt| (codec.pt.to_string(), rtx_pt as u32))
})
.collect(); .collect();
{ {
let pts = pts.clone(); let pts = pts.clone();
@ -777,7 +795,8 @@ impl JingleSession {
let source_element = match source.media_type { let source_element = match source.media_type {
MediaType::Audio => { MediaType::Audio => {
let codec = codecs.iter() let codec = codecs
.iter()
.filter(|codec| codec.is_audio()) .filter(|codec| codec.is_audio())
.find(|codec| codec.is(pt)); .find(|codec| codec.is(pt));
if let Some(codec) = codec { if let Some(codec) = codec {
@ -788,7 +807,8 @@ impl JingleSession {
} }
}, },
MediaType::Video => { MediaType::Video => {
let codec = codecs.iter() let codec = codecs
.iter()
.filter(|codec| codec.is_video()) .filter(|codec| codec.is_video())
.find(|codec| codec.is(pt)); .find(|codec| codec.is(pt));
if let Some(codec) = codec { if let Some(codec) = codec {
@ -902,7 +922,8 @@ impl JingleSession {
let audio_sink_element = gstreamer::ElementFactory::make(opus.make_pay_name(), None)?; let audio_sink_element = gstreamer::ElementFactory::make(opus.make_pay_name(), None)?;
audio_sink_element.set_property("pt", opus.pt as u32); audio_sink_element.set_property("pt", opus.pt as u32);
audio_sink_element audio_sink_element
} else { }
else {
bail!("no opus payload type in jingle session-initiate"); bail!("no opus payload type in jingle session-initiate");
}; };
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000); audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000);
@ -1045,7 +1066,7 @@ impl JingleSession {
debug!("pipeline state is null"); debug!("pipeline state is null");
pipeline_state_null_tx.send(()).unwrap(); pipeline_state_null_tx.send(()).unwrap();
break; break;
} },
_ => {}, _ => {},
} }
} }
@ -1077,12 +1098,7 @@ impl JingleSession {
description.payload_types = if initiate_content.name.0 == "audio" { description.payload_types = if initiate_content.name.0 == "audio" {
let codec = codecs.iter().find(|codec| codec.name == CodecName::Opus); let codec = codecs.iter().find(|codec| codec.name == CodecName::Opus);
if let Some(codec) = codec { if let Some(codec) = codec {
let mut pt = PayloadType::new( let mut pt = PayloadType::new(codec.pt, "opus".to_owned(), 48000, 2);
codec.pt,
"opus".to_owned(),
48000,
2,
);
pt.rtcp_fbs = codec.rtcp_fbs.clone(); pt.rtcp_fbs = codec.rtcp_fbs.clone();
vec![pt] vec![pt]
} }
@ -1168,18 +1184,16 @@ impl JingleSession {
)); ));
} }
if let Some(hdrext) = audio_hdrext_transport_cc { if let Some(hdrext) = audio_hdrext_transport_cc {
description.hdrexts.push(RtpHdrext::new( description
hdrext, .hdrexts
RTP_HDREXT_TRANSPORT_CC.to_owned(), .push(RtpHdrext::new(hdrext, RTP_HDREXT_TRANSPORT_CC.to_owned()));
));
} }
} }
else if initiate_content.name.0 == "video" { else if initiate_content.name.0 == "video" {
if let Some(hdrext) = video_hdrext_transport_cc { if let Some(hdrext) = video_hdrext_transport_cc {
description.hdrexts.push(RtpHdrext::new( description
hdrext, .hdrexts
RTP_HDREXT_TRANSPORT_CC.to_owned(), .push(RtpHdrext::new(hdrext, RTP_HDREXT_TRANSPORT_CC.to_owned()));
));
} }
} }
@ -1227,10 +1241,7 @@ impl JingleSession {
jingle_accept = jingle_accept.set_group(jingle_grouping::Group { jingle_accept = jingle_accept.set_group(jingle_grouping::Group {
semantics: jingle_grouping::Semantics::Bundle, semantics: jingle_grouping::Semantics::Bundle,
contents: vec![ contents: vec![GroupContent::new("video"), GroupContent::new("audio")],
GroupContent::new("video"),
GroupContent::new("audio"),
],
}); });
let accept_iq_id = generate_id(); let accept_iq_id = generate_id();

View File

@ -1,4 +1,7 @@
#[cfg(any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots"))] #[cfg(any(
feature = "tls-rustls-native-roots",
feature = "tls-rustls-webpki-roots"
))]
use std::sync::Arc; use std::sync::Arc;
#[cfg(not(feature = "tls-insecure"))] #[cfg(not(feature = "tls-insecure"))]
@ -19,11 +22,15 @@ pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connect
.with_no_client_auth(); .with_no_client_auth();
#[cfg(feature = "tls-insecure")] #[cfg(feature = "tls-insecure")]
if insecure { if insecure {
config.dangerous().set_certificate_verifier(Arc::new(InsecureServerCertVerifier)); config
.dangerous()
.set_certificate_verifier(Arc::new(InsecureServerCertVerifier));
} }
#[cfg(not(feature = "tls-insecure"))] #[cfg(not(feature = "tls-insecure"))]
if insecure { if insecure {
bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") bail!(
"Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time."
)
} }
Ok(Connector::Rustls(Arc::new(config))) Ok(Connector::Rustls(Arc::new(config)))
} }
@ -31,15 +38,13 @@ pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connect
#[cfg(feature = "tls-rustls-webpki-roots")] #[cfg(feature = "tls-rustls-webpki-roots")]
pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connector> { pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connector> {
let mut roots = rustls::RootCertStore::empty(); let mut roots = rustls::RootCertStore::empty();
roots.add_server_trust_anchors( roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject,
ta.subject, ta.spki,
ta.spki, ta.name_constraints,
ta.name_constraints, )
) }));
})
);
let config = rustls::ClientConfig::builder() let config = rustls::ClientConfig::builder()
.with_safe_defaults() .with_safe_defaults()
@ -47,11 +52,15 @@ pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connect
.with_no_client_auth(); .with_no_client_auth();
#[cfg(feature = "tls-insecure")] #[cfg(feature = "tls-insecure")]
if insecure { if insecure {
config.dangerous().set_certificate_verifier(Arc::new(InsecureServerCertVerifier)); config
.dangerous()
.set_certificate_verifier(Arc::new(InsecureServerCertVerifier));
} }
#[cfg(not(feature = "tls-insecure"))] #[cfg(not(feature = "tls-insecure"))]
if insecure { if insecure {
bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") bail!(
"Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time."
)
} }
Ok(Connector::Rustls(Arc::new(config))) Ok(Connector::Rustls(Arc::new(config)))
} }
@ -67,17 +76,39 @@ pub(crate) fn wss_connector(insecure: bool) -> Result<tokio_tungstenite::Connect
} }
#[cfg(not(feature = "tls-insecure"))] #[cfg(not(feature = "tls-insecure"))]
if insecure { if insecure {
bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") bail!(
"Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time."
)
} }
Ok(Connector::NativeTls(builder.build()?)) Ok(Connector::NativeTls(builder.build()?))
} }
#[cfg(all(feature = "tls-insecure", any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots")))] #[cfg(all(
feature = "tls-insecure",
any(
feature = "tls-rustls-native-roots",
feature = "tls-rustls-webpki-roots"
)
))]
struct InsecureServerCertVerifier; struct InsecureServerCertVerifier;
#[cfg(all(feature = "tls-insecure", any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots")))] #[cfg(all(
feature = "tls-insecure",
any(
feature = "tls-rustls-native-roots",
feature = "tls-rustls-webpki-roots"
)
))]
impl rustls::client::ServerCertVerifier for InsecureServerCertVerifier { impl rustls::client::ServerCertVerifier for InsecureServerCertVerifier {
fn verify_server_cert(&self, _end_entity: &rustls::Certificate, _intermediates: &[rustls::Certificate], _server_name: &rustls::ServerName, _scts: &mut dyn Iterator<Item = &[u8]>, _ocsp_response: &[u8], _now: std::time::SystemTime) -> Result<rustls::client::ServerCertVerified, rustls::Error> { fn verify_server_cert(
&self,
_end_entity: &rustls::Certificate,
_intermediates: &[rustls::Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
Ok(rustls::client::ServerCertVerified::assertion()) Ok(rustls::client::ServerCertVerified::assertion())
} }
} }

View File

@ -5,7 +5,7 @@ use futures::{
sink::{Sink, SinkExt}, sink::{Sink, SinkExt},
stream::{Stream, StreamExt, TryStreamExt}, stream::{Stream, StreamExt, TryStreamExt},
}; };
use rand::{RngCore, thread_rng}; use rand::{thread_rng, RngCore};
use tokio::sync::{mpsc, oneshot, Mutex}; use tokio::sync::{mpsc, oneshot, Mutex};
use tokio_stream::wrappers::ReceiverStream; use tokio_stream::wrappers::ReceiverStream;
use tokio_tungstenite::tungstenite::{ use tokio_tungstenite::tungstenite::{
@ -22,7 +22,9 @@ use xmpp_parsers::{
BareJid, Element, FullJid, Jid, BareJid, Element, FullJid, Jid,
}; };
use crate::{pinger::Pinger, stanza_filter::StanzaFilter, tls::wss_connector, util::generate_id, xmpp}; use crate::{
pinger::Pinger, stanza_filter::StanzaFilter, tls::wss_connector, util::generate_id, xmpp,
};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum ConnectionState { enum ConnectionState {
@ -85,14 +87,23 @@ impl Connection {
.header("sec-websocket-protocol", "xmpp") .header("sec-websocket-protocol", "xmpp")
.header("sec-websocket-key", base64::encode(&key)) .header("sec-websocket-key", base64::encode(&key))
.header("sec-websocket-version", "13") .header("sec-websocket-version", "13")
.header("host", websocket_url.host().context("invalid WebSocket URL: missing host")?) .header(
"host",
websocket_url
.host()
.context("invalid WebSocket URL: missing host")?,
)
.header("connection", "Upgrade") .header("connection", "Upgrade")
.header("upgrade", "websocket") .header("upgrade", "websocket")
.body(()) .body(())
.context("failed to build WebSocket request")?; .context("failed to build WebSocket request")?;
let (websocket, _response) = tokio_tungstenite::connect_async_tls_with_config(request, None, Some(wss_connector(tls_insecure)?)) let (websocket, _response) = tokio_tungstenite::connect_async_tls_with_config(
.await request,
.context("failed to connect XMPP WebSocket")?; None,
Some(wss_connector(tls_insecure)?),
)
.await
.context("failed to connect XMPP WebSocket")?;
let (sink, stream) = websocket.split(); let (sink, stream) = websocket.split();
let (tx, rx) = mpsc::channel(64); let (tx, rx) = mpsc::channel(64);

View File

@ -2,12 +2,7 @@
// from ../../gir-files (@ 8e47c67) // from ../../gir-files (@ 8e47c67)
// DO NOT EDIT // DO NOT EDIT
use std::{ use std::{boxed::Box as Box_, fmt, mem::transmute, ptr, slice};
boxed::Box as Box_,
fmt,
mem::transmute,
ptr, slice,
};
use glib::{ use glib::{
ffi::gpointer, ffi::gpointer,
@ -48,7 +43,8 @@ extern "C" fn attach_recv_cb(
user_data: gpointer, user_data: gpointer,
) { ) {
if !user_data.is_null() { if !user_data.is_null() {
let closure: &mut Box<dyn FnMut(Agent, u32, u32, &str)> = unsafe { &mut *(user_data as *mut _) }; let closure: &mut Box<dyn FnMut(Agent, u32, u32, &str)> =
unsafe { &mut *(user_data as *mut _) };
let slice = unsafe { slice::from_raw_parts(data, len as usize) }; let slice = unsafe { slice::from_raw_parts(data, len as usize) };
let bytes: Vec<_> = slice.iter().map(|b| *b as u8).collect(); let bytes: Vec<_> = slice.iter().map(|b| *b as u8).collect();
if let Ok(s) = std::str::from_utf8(&bytes) { if let Ok(s) = std::str::from_utf8(&bytes) {