fmt
This commit is contained in:
parent
c68a7cbe0a
commit
44208e28b3
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(":"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 initiator’s JID.
|
/// Set the initiator’s 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 responder’s JID.
|
/// Set the responder’s 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 isn’t known at compile-time.
|
/// To be used for any description that isn’t 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 isn’t known at compile-time.
|
/// To be used for any transport that isn’t 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue