use std::collections::HashMap;
use livekit::id::ParticipantIdentity;
use tracing::error;
pub(crate) const PROTOCOL_VERSION_ATTRIBUTE: &str = "protocolVersion";
pub(crate) const REMOTE_ACCESS_PROTOCOL_VERSION: semver::Version = semver::Version::new(2, 2, 0);
pub(crate) const REMOTE_ACCESS_MIN_SUPPORTED_PROTOCOL_VERSION: semver::Version =
semver::Version::new(2, 2, 0);
pub(crate) const DEFAULT_PROTOCOL_VERSION: semver::Version = semver::Version::new(2, 2, 0);
pub(crate) fn parse_participant_protocol_version(
attributes: &HashMap<String, String>,
) -> Option<semver::Version> {
let Some(version_str) = attributes.get(PROTOCOL_VERSION_ATTRIBUTE) else {
return Some(DEFAULT_PROTOCOL_VERSION.clone());
};
match semver::Version::parse(version_str) {
Ok(v) => Some(v),
Err(e) => {
error!(
version = version_str.as_str(),
"failed to parse participant protocol version: {e}"
);
None
}
}
}
pub(crate) fn check_participant_protocol_version(
participant_identity: &ParticipantIdentity,
attributes: &HashMap<String, String>,
remote_access_session_id: Option<&str>,
) -> Option<semver::Version> {
let version = parse_participant_protocol_version(attributes)?;
if version < REMOTE_ACCESS_MIN_SUPPORTED_PROTOCOL_VERSION {
error!(
remote_access_session_id,
participant_identity = %participant_identity,
participant_version = %version,
min_supported_version = %REMOTE_ACCESS_MIN_SUPPORTED_PROTOCOL_VERSION,
"participant protocol version is below minimum supported; ignoring participant"
);
return None;
}
if version.major != REMOTE_ACCESS_PROTOCOL_VERSION.major {
error!(
remote_access_session_id,
participant_identity = %participant_identity,
participant_version = %version,
our_version = %REMOTE_ACCESS_PROTOCOL_VERSION,
"participant protocol version has incompatible major version; ignoring participant"
);
return None;
}
Some(version)
}
#[cfg(test)]
mod tests {
use super::*;
fn attrs(version: &str) -> HashMap<String, String> {
HashMap::from([(PROTOCOL_VERSION_ATTRIBUTE.to_string(), version.to_string())])
}
fn identity() -> ParticipantIdentity {
ParticipantIdentity::from("test-participant".to_string())
}
#[test]
fn parse_valid_version() {
let result = parse_participant_protocol_version(&attrs("2.2.0"));
assert_eq!(result, Some(semver::Version::new(2, 2, 0)));
}
#[test]
fn parse_missing_attribute_defaults_to_current() {
let result = parse_participant_protocol_version(&HashMap::new());
assert_eq!(result, Some(semver::Version::new(2, 2, 0)));
}
#[test]
fn parse_garbage_string_returns_none() {
let result = parse_participant_protocol_version(&attrs("not-a-version"));
assert_eq!(result, None);
}
#[test]
fn parse_empty_string_returns_none() {
let result = parse_participant_protocol_version(&attrs(""));
assert_eq!(result, None);
}
#[test]
fn check_valid_version_at_minimum_returns_some() {
let result = check_participant_protocol_version(&identity(), &attrs("2.2.0"), None);
assert_eq!(result, Some(semver::Version::new(2, 2, 0)));
}
#[test]
fn check_valid_version_above_minimum_returns_some() {
let result = check_participant_protocol_version(&identity(), &attrs("2.2.1"), Some("sess"));
assert_eq!(result, Some(semver::Version::new(2, 2, 1)));
}
#[test]
fn check_missing_attribute_defaults_and_passes() {
let result = check_participant_protocol_version(&identity(), &HashMap::new(), None);
assert_eq!(result, Some(semver::Version::new(2, 2, 0)));
}
#[test]
fn check_version_below_minimum_returns_none() {
let result = check_participant_protocol_version(&identity(), &attrs("2.1.9"), None);
assert_eq!(result, None);
}
#[test]
fn check_future_major_version_returns_none() {
let result = check_participant_protocol_version(&identity(), &attrs("3.0.0"), None);
assert_eq!(result, None);
}
#[test]
fn check_garbage_string_returns_none() {
let result = check_participant_protocol_version(&identity(), &attrs("garbage"), None);
assert_eq!(result, None);
}
}