implement caps to avoid a panic

The previous disco#info was missing the required disco#info feature and
an identity, and the ecaps2 was computed from an empty disco#info
instead of the one we advertise.

In our case caps is a tiny optimisation which lets other participants
cache our disco#info to avoid querying us every time.

A future improvement will be to reply to ecaps2 queries as well.
This commit is contained in:
Emmanuel Gil Peyrot 2021-10-23 23:35:37 +02:00 committed by Jasper
parent c793d08bb6
commit 66cfaa4da5
1 changed files with 42 additions and 17 deletions

View File

@ -14,9 +14,10 @@ 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}, disco::{DiscoInfoQuery, DiscoInfoResult, Identity},
caps::{self, Caps},
ecaps2::{self, ECaps2}, ecaps2::{self, ECaps2},
hashes::Algo, hashes::{Algo, Hash},
iq::{Iq, IqType}, iq::{Iq, IqType},
jingle::{Action, Jingle}, jingle::{Action, Jingle},
message::{Message, MessageType}, message::{Message, MessageType},
@ -24,6 +25,7 @@ use xmpp_parsers::{
nick::Nick, nick::Nick,
ns, ns,
presence::{self, Presence}, presence::{self, Presence},
stanza_error::{DefinedCondition, ErrorType, StanzaError},
BareJid, Element, FullJid, Jid, BareJid, Element, FullJid, Jid,
}; };
@ -36,8 +38,11 @@ use crate::{
xmpp::{self, connection::Connection}, xmpp::{self, connection::Connection},
}; };
const DISCO_NODE: &str = "https://github.com/avstack/gst-meet";
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| { static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
let mut features = vec![ let mut features = vec![
Feature::new(ns::DISCO_INFO),
Feature::new(ns::JINGLE_RTP_AUDIO), Feature::new(ns::JINGLE_RTP_AUDIO),
Feature::new(ns::JINGLE_RTP_VIDEO), Feature::new(ns::JINGLE_RTP_VIDEO),
Feature::new(ns::JINGLE_ICE_UDP), Feature::new(ns::JINGLE_ICE_UDP),
@ -54,16 +59,23 @@ static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
else { else {
warn!("Upgrade GStreamer to 1.19 or later to enable RTP header extensions"); warn!("Upgrade GStreamer to 1.19 or later to enable RTP header extensions");
} }
let identities = vec![
Identity::new("client", "bot", "en", "gst-meet"),
];
// Not supported yet: // Not supported yet:
// Feature::new("http://jitsi.org/opus-red") // Feature::new("http://jitsi.org/opus-red")
DiscoInfoResult { DiscoInfoResult {
node: None, node: None,
identities: vec![], identities,
features, features,
extensions: vec![], extensions: vec![],
} }
}); });
static COMPUTED_CAPS_HASH: Lazy<Hash> = 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 {
Discovering, Discovering,
@ -437,18 +449,12 @@ impl StanzaFilter for JitsiConference {
bail!("focus IQ failed"); bail!("focus IQ failed");
}; };
let jitsi_disco_info = DiscoInfoResult { let ecaps2_hash =
node: Some("http://jitsi.org/jitsimeet".to_string()), ecaps2::hash_ecaps2(&ecaps2::compute_disco(&DISCO_INFO)?, Algo::Sha_256)?;
identities: vec![],
features: vec![],
extensions: vec![],
};
let jitsi_disco_hash =
ecaps2::hash_ecaps2(&ecaps2::compute_disco(&jitsi_disco_info)?, Algo::Sha_256)?;
let mut presence = vec![ let mut presence = vec![
Muc::new().into(), Muc::new().into(),
ECaps2::new(vec![jitsi_disco_hash]).into(), Caps::new(DISCO_NODE, COMPUTED_CAPS_HASH.clone()).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(self.config.video_codec.as_str()) .append(self.config.video_codec.as_str())
@ -501,15 +507,34 @@ impl StanzaFilter for JitsiConference {
iq.from.as_ref().unwrap(), iq.from.as_ref().unwrap(),
query.node query.node
); );
if query.node.is_none() { if let Some(node) = query.node {
match node.splitn(2, '#').collect::<Vec<_>>().as_slice() {
// TODO: also support ecaps2, as we send it in our presence.
[uri, hash] if *uri == DISCO_NODE && *hash == COMPUTED_CAPS_HASH.to_base64() => {
let mut disco_info = DISCO_INFO.clone();
disco_info.node = Some(node);
let iq = Iq::from_result(iq.id, Some(disco_info))
.with_from(Jid::Full(self.jid.clone()))
.with_to(iq.from.unwrap());
self.xmpp_tx.send(iq.into()).await?;
}
_ => {
let error = StanzaError::new(
ErrorType::Cancel, DefinedCondition::ItemNotFound,
"en", format!("Unknown disco#info node: {}", node));
let iq = Iq::from_error(iq.id, error)
.with_from(Jid::Full(self.jid.clone()))
.with_to(iq.from.unwrap());
self.xmpp_tx.send(iq.into()).await?;
}
}
}
else {
let iq = Iq::from_result(iq.id, Some(DISCO_INFO.clone())) let iq = Iq::from_result(iq.id, Some(DISCO_INFO.clone()))
.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 {
panic!("don't know how to handle disco info node: {:?}", query.node);
}
} }
}, },
IqType::Set(element) => { IqType::Set(element) => {