Start on implementing a fake live-kit server

This commit is contained in:
Antonio Scandurra 2022-10-19 14:58:50 +02:00
parent fb5c6493cf
commit 288c039929
6 changed files with 134 additions and 35 deletions

2
Cargo.lock generated
View file

@ -3167,6 +3167,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes 1.2.1", "bytes 1.2.1",
"cocoa", "cocoa",
"collections",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"foreign-types", "foreign-types",
@ -3174,6 +3175,7 @@ dependencies = [
"gpui", "gpui",
"hmac 0.12.1", "hmac 0.12.1",
"jwt", "jwt",
"lazy_static",
"live_kit_server", "live_kit_server",
"log", "log",
"media", "media",

View file

@ -12,19 +12,25 @@ doctest = false
name = "test_app" name = "test_app"
[features] [features]
test-support = [] test-support = ["collections/test-support", "gpui/test-support", "lazy_static", "live_kit_server"]
[dependencies] [dependencies]
collections = { path = "../collections", optional = true }
gpui = { path = "../gpui", optional = true }
live_kit_server = { path = "../live_kit_server", optional = true }
media = { path = "../media" } media = { path = "../media" }
anyhow = "1.0.38" anyhow = "1.0.38"
core-foundation = "0.9.3" core-foundation = "0.9.3"
core-graphics = "0.22.3" core-graphics = "0.22.3"
futures = "0.3" futures = "0.3"
lazy_static = { version = "1.4", optional = true }
parking_lot = "0.11.1" parking_lot = "0.11.1"
[dev-dependencies] [dev-dependencies]
gpui = { path = "../gpui" } collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
lazy_static = "1.4"
live_kit_server = { path = "../live_kit_server" } live_kit_server = { path = "../live_kit_server" }
media = { path = "../media" } media = { path = "../media" }

View file

@ -1,8 +1,10 @@
pub mod prod; pub mod prod;
pub mod test;
#[cfg(not(any(test, feature = "test-support")))] #[cfg(not(any(test, feature = "test-support")))]
pub use prod::*; pub use prod::*;
#[cfg(any(test, feature = "test-support"))]
mod test;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub use test::*; pub use test::*;

View file

@ -1,8 +1,80 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use collections::HashMap;
use futures::{channel::mpsc, future}; use futures::{channel::mpsc, future};
use gpui::executor::Background;
use lazy_static::lazy_static;
use media::core_video::CVImageBuffer; use media::core_video::CVImageBuffer;
use parking_lot::Mutex;
use std::{future::Future, sync::Arc}; use std::{future::Future, sync::Arc};
lazy_static! {
static ref SERVERS: Mutex<HashMap<String, Arc<FakeServer>>> = Default::default();
}
pub struct FakeServer {
url: String,
secret_key: String,
rooms: Mutex<HashMap<String, FakeServerRoom>>,
background: Arc<Background>,
}
impl FakeServer {
pub fn create(
url: String,
secret_key: String,
background: Arc<Background>,
) -> Result<Arc<FakeServer>> {
let mut servers = SERVERS.lock();
if servers.contains_key(&url) {
Err(anyhow!("a server with url {:?} already exists", url))
} else {
let server = Arc::new(FakeServer {
url: url.clone(),
secret_key,
rooms: Default::default(),
background,
});
servers.insert(url, server.clone());
Ok(server)
}
}
fn get(url: &str) -> Result<Arc<FakeServer>> {
Ok(SERVERS
.lock()
.get(url)
.ok_or_else(|| anyhow!("no server found for url"))?
.clone())
}
pub fn teardown(&self) -> Result<()> {
SERVERS
.lock()
.remove(&self.url)
.ok_or_else(|| anyhow!("server with url {:?} does not exist", self.url))?;
Ok(())
}
async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
self.background.simulate_random_delay().await;
let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
let identity = claims.sub.unwrap().to_string();
let room = claims.video.room.unwrap();
let mut server_rooms = self.rooms.lock();
let room = server_rooms
.get_mut(&*room)
.ok_or_else(|| anyhow!("room {} does not exist", room))?;
room.clients.insert(identity, client_room);
Ok(())
}
}
struct FakeServerRoom {
clients: HashMap<Sid, Arc<Room>>,
}
impl FakeServerRoom {}
pub type Sid = String; pub type Sid = String;
pub struct Room; pub struct Room;
@ -12,8 +84,15 @@ impl Room {
Arc::new(Self) Arc::new(Self)
} }
pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> { pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
future::pending() let this = self.clone();
let url = url.to_string();
let token = token.to_string();
async move {
let server = FakeServer::get(&url)?;
server.join_room(token, this).await?;
Ok(())
}
} }
pub fn publish_video_track( pub fn publish_video_track(
@ -34,6 +113,12 @@ impl Room {
} }
} }
impl Drop for Room {
fn drop(&mut self) {
todo!()
}
}
pub struct LocalTrackPublication; pub struct LocalTrackPublication;
pub struct LocalVideoTrack; pub struct LocalVideoTrack;

View file

@ -66,11 +66,7 @@ impl Client {
) -> impl Future<Output = Result<()>> { ) -> impl Future<Output = Result<()>> {
let response = self.request( let response = self.request(
"twirp/livekit.RoomService/RemoveParticipant", "twirp/livekit.RoomService/RemoveParticipant",
token::VideoGrant { token::VideoGrant::to_admin(&room),
room_admin: Some(true),
room: Some(&room),
..Default::default()
},
proto::RoomParticipantIdentity { proto::RoomParticipantIdentity {
room: room.clone(), room: room.clone(),
identity, identity,
@ -87,13 +83,7 @@ impl Client {
&self.key, &self.key,
&self.secret, &self.secret,
Some(identity), Some(identity),
token::VideoGrant { token::VideoGrant::to_join(room),
room: Some(room),
room_join: Some(true),
can_publish: Some(true),
can_subscribe: Some(true),
..Default::default()
},
) )
} }

View file

@ -1,28 +1,29 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use jwt::SignWithKey; use jwt::{SignWithKey, VerifyWithKey};
use serde::Serialize; use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use std::{ use std::{
borrow::Cow,
ops::Add, ops::Add,
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours
#[derive(Default, Serialize)] #[derive(Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ClaimGrants<'a> { pub struct ClaimGrants<'a> {
iss: &'a str, pub iss: Cow<'a, str>,
sub: Option<&'a str>, pub sub: Option<Cow<'a, str>>,
iat: u64, pub iat: u64,
exp: u64, pub exp: u64,
nbf: u64, pub nbf: u64,
jwtid: Option<&'a str>, pub jwtid: Option<Cow<'a, str>>,
video: VideoGrant<'a>, pub video: VideoGrant<'a>,
} }
#[derive(Default, Serialize)] #[derive(Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoGrant<'a> { pub struct VideoGrant<'a> {
pub room_create: Option<bool>, pub room_create: Option<bool>,
@ -30,7 +31,7 @@ pub struct VideoGrant<'a> {
pub room_list: Option<bool>, pub room_list: Option<bool>,
pub room_record: Option<bool>, pub room_record: Option<bool>,
pub room_admin: Option<bool>, pub room_admin: Option<bool>,
pub room: Option<&'a str>, pub room: Option<Cow<'a, str>>,
pub can_publish: Option<bool>, pub can_publish: Option<bool>,
pub can_subscribe: Option<bool>, pub can_subscribe: Option<bool>,
pub can_publish_data: Option<bool>, pub can_publish_data: Option<bool>,
@ -39,9 +40,17 @@ pub struct VideoGrant<'a> {
} }
impl<'a> VideoGrant<'a> { impl<'a> VideoGrant<'a> {
pub fn to_admin(room: &'a str) -> Self {
Self {
room_admin: Some(true),
room: Some(Cow::Borrowed(room)),
..Default::default()
}
}
pub fn to_join(room: &'a str) -> Self { pub fn to_join(room: &'a str) -> Self {
Self { Self {
room: Some(room), room: Some(Cow::Borrowed(room)),
room_join: Some(true), room_join: Some(true),
can_publish: Some(true), can_publish: Some(true),
can_subscribe: Some(true), can_subscribe: Some(true),
@ -67,8 +76,8 @@ pub fn create(
let now = SystemTime::now(); let now = SystemTime::now();
let claims = ClaimGrants { let claims = ClaimGrants {
iss: api_key, iss: Cow::Borrowed(api_key),
sub: identity, sub: identity.map(Cow::Borrowed),
iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(), iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
exp: now exp: now
.add(DEFAULT_TTL) .add(DEFAULT_TTL)
@ -76,8 +85,13 @@ pub fn create(
.unwrap() .unwrap()
.as_secs(), .as_secs(),
nbf: 0, nbf: 0,
jwtid: identity, jwtid: identity.map(Cow::Borrowed),
video: video_grant, video: video_grant,
}; };
Ok(claims.sign_with_key(&secret_key)?) Ok(claims.sign_with_key(&secret_key)?)
} }
pub fn validate<'a>(token: &'a str, secret_key: &str) -> Result<ClaimGrants<'a>> {
let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
Ok(token.verify_with_key(&secret_key)?)
}