From 053ed8c0540a4b6f67c8ffca21e9d9032fd47947 Mon Sep 17 00:00:00 2001 From: restitux Date: Fri, 18 Jul 2025 18:07:33 -0600 Subject: [PATCH] meta: renamed config to state and wrapped in a server object --- gamestream-webtransport-proxy/src/apps.rs | 6 +- gamestream-webtransport-proxy/src/config.rs | 158 -------------------- gamestream-webtransport-proxy/src/main.rs | 11 +- gamestream-webtransport-proxy/src/pair.rs | 14 +- gamestream-webtransport-proxy/src/server.rs | 16 ++ gamestream-webtransport-proxy/src/state.rs | 158 ++++++++++++++++++++ 6 files changed, 190 insertions(+), 173 deletions(-) delete mode 100644 gamestream-webtransport-proxy/src/config.rs create mode 100644 gamestream-webtransport-proxy/src/server.rs create mode 100644 gamestream-webtransport-proxy/src/state.rs diff --git a/gamestream-webtransport-proxy/src/apps.rs b/gamestream-webtransport-proxy/src/apps.rs index ac3ffb8..abe0050 100644 --- a/gamestream-webtransport-proxy/src/apps.rs +++ b/gamestream-webtransport-proxy/src/apps.rs @@ -4,7 +4,7 @@ use salvo::prelude::*; use serde::{Deserialize, Serialize}; use tracing::{debug, error}; -use crate::{common::AppResult, config::ConfigReader}; +use crate::{common::AppResult, state::StateReader}; #[derive(Deserialize)] struct AppListRespApp { @@ -37,7 +37,7 @@ struct GetAppsResponse { } #[craft] -impl crate::config::ConfigFile { +impl crate::server::Server { #[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))] pub async fn get_apps(self: ::std::sync::Arc) -> AppResult> { let standard_error = Err(crate::common::AppError { @@ -45,7 +45,7 @@ impl crate::config::ConfigFile { description: "failed to get available apps".to_string(), }); - let reader = self.read().await; + let reader = self.state.read().await; let unique_id = match reader.unique_id() { Ok(u) => u, Err(e) => { diff --git a/gamestream-webtransport-proxy/src/config.rs b/gamestream-webtransport-proxy/src/config.rs deleted file mode 100644 index 90b5cb2..0000000 --- a/gamestream-webtransport-proxy/src/config.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::{ - collections::HashMap, - fs::{self, File}, - path::PathBuf, -}; - -use anyhow::{Result, anyhow}; -use serde::{Deserialize, Serialize}; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; - -#[derive(Serialize, Deserialize)] -pub struct Server { - pub name: String, - pub host: String, - pub base_port: u16, -} - -impl Server { - pub fn http_port(&self) -> u16 { - self.base_port + 189 - } - pub fn https_port(&self) -> u16 { - self.base_port + 184 - } -} - -#[derive(Serialize, Deserialize)] -struct Config { - servers: HashMap, - unique_id: String, -} - -pub struct ConfigFile { - lock: tokio::sync::RwLock<()>, - path: PathBuf, -} - -pub trait ConfigReader { - fn servers(&self) -> Result>; - fn unique_id(&self) -> Result; -} - -pub trait ConfigWriter { - fn add_server(&self, server: Server) -> Result<()>; -} - -pub struct ConfigReadAccess<'a> { - _guard: RwLockReadGuard<'a, ()>, - config: &'a ConfigFile, -} - -pub struct ConfigWriteAccess<'a> { - _guard: RwLockWriteGuard<'a, ()>, - config: &'a ConfigFile, -} - -impl ConfigFile { - fn load_config(&self) -> Result { - tracing::debug!("parsing config file"); - - let config_file = File::open(&self.path)?; - Ok(serde_json::from_reader(config_file)?) - } - - fn write_config(&self, config: Config) -> Result<()> { - tracing::debug!("serializing config file"); - let config_file = File::create(&self.path)?; - Ok(serde_json::to_writer_pretty(config_file, &config)?) - } - - pub fn new() -> Result { - let project_dirs = - directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy") - .ok_or(anyhow::anyhow!("Could not get project dirs"))?; - - let data_dir = project_dirs.data_dir(); - fs::create_dir_all(data_dir)?; - - let state_path = data_dir.join("state.json"); - - if let Err(e) = File::open(&state_path) { - if e.kind() == std::io::ErrorKind::NotFound { - write_default_config_to_path(&state_path)?; - } else { - return Err(anyhow!(e)); - } - } - - Ok(ConfigFile { - lock: tokio::sync::RwLock::new(()), - path: state_path, - }) - } -} - -impl ConfigFile { - pub async fn read(&self) -> ConfigReadAccess { - ConfigReadAccess { - _guard: self.lock.read().await, - config: self, - } - } - - pub async fn write(&self) -> ConfigWriteAccess { - ConfigWriteAccess { - _guard: self.lock.write().await, - config: self, - } - } -} - -impl<'a> ConfigReader for ConfigReadAccess<'a> { - fn servers(&self) -> Result> { - let config = self.config.load_config()?; - Ok(config.servers) - } - - fn unique_id(&self) -> Result { - let config = self.config.load_config()?; - Ok(config.unique_id) - } -} - -impl<'a> ConfigWriter for ConfigWriteAccess<'a> { - fn add_server(&self, server: Server) -> Result<()> { - let mut config = self.config.load_config()?; - - if config.servers.contains_key(&server.name) { - return Err(anyhow!( - "cannot add duplicate server with name: {}", - server.name - )); - } - - config.servers.insert(server.name.clone(), server); - - self.config.write_config(config)?; - - Ok(()) - } -} - -pub fn get_unique_id() -> Result { - let mut bytes = [0u8; 8]; - openssl::rand::rand_bytes(&mut bytes)?; - Ok(hex::encode(bytes)) -} - -fn write_default_config_to_path(path: &PathBuf) -> Result<()> { - let default_config = Config { - servers: HashMap::new(), - unique_id: get_unique_id()?, - }; - - let config_file = File::create(path)?; - - Ok(serde_json::to_writer_pretty(config_file, &default_config)?) -} diff --git a/gamestream-webtransport-proxy/src/main.rs b/gamestream-webtransport-proxy/src/main.rs index eddb306..bac6662 100644 --- a/gamestream-webtransport-proxy/src/main.rs +++ b/gamestream-webtransport-proxy/src/main.rs @@ -10,8 +10,9 @@ use moonlight_common_c_sys::{ mod apps; mod certs; mod common; -mod config; mod pair; +mod server; +mod state; #[allow(unused)] fn get_server_info() -> _SERVER_INFORMATION { @@ -113,12 +114,12 @@ async fn main() -> anyhow::Result<()> { .with_max_level(tracing::Level::DEBUG) .init(); - let config = config::ConfigFile::new()?; - let config_arc = std::sync::Arc::new(config); + let server = server::Server::new()?; + let server_arc = std::sync::Arc::new(server); let router = Router::new() - .push(Router::with_path("pair").post(config_arc.post_pair())) - .push(Router::with_path("apps").get(config_arc.get_apps())); + .push(Router::with_path("pair").post(server_arc.post_pair())) + .push(Router::with_path("apps").get(server_arc.get_apps())); let doc = OpenApi::new("test api", "0.0.1").merge_router(&router); let router = router .unshift(doc.into_router("/api-doc/openapi.json")) diff --git a/gamestream-webtransport-proxy/src/pair.rs b/gamestream-webtransport-proxy/src/pair.rs index 9aeb171..9e3914c 100644 --- a/gamestream-webtransport-proxy/src/pair.rs +++ b/gamestream-webtransport-proxy/src/pair.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, error, info}; use crate::common::{AppError, AppResult, get_url}; -use crate::config::{ConfigReader, ConfigWriter}; +use crate::state::{StateReader, StateWriter}; #[derive(Debug, Deserialize, ToSchema)] struct PostPairParams { @@ -291,7 +291,7 @@ async fn send_client_pairing_secret( } #[craft] -impl crate::config::ConfigFile { +impl crate::server::Server { #[craft(endpoint(status_codes( StatusCode::OK, StatusCode::BAD_REQUEST, @@ -303,13 +303,13 @@ impl crate::config::ConfigFile { ) -> AppResult<()> { let params = body.into_inner(); - let server = crate::config::Server { + let server = crate::state::GamestreamServer { host: params.host, base_port: params.base_port, name: params.name, }; - let reader = self.read().await; + let reader = self.state.read().await; let servers = match reader.servers() { Ok(s) => s, @@ -333,7 +333,7 @@ impl crate::config::ConfigFile { }); } - let unique_id = match self.read().await.unique_id() { + let unique_id = match self.state.read().await.unique_id() { Ok(u) => u, Err(e) => { error!("Could not get unique id: {e}"); @@ -457,10 +457,10 @@ impl crate::config::ConfigFile { ); } - match self.write().await.add_server(server) { + match self.state.write().await.add_server(server) { Ok(_) => (), Err(e) => { - error!("Could not write to config file: {e}"); + error!("Could not write to state file: {e}"); return Err(AppError { status_code: StatusCode::INTERNAL_SERVER_ERROR, description: "Pairing failed".to_string(), diff --git a/gamestream-webtransport-proxy/src/server.rs b/gamestream-webtransport-proxy/src/server.rs new file mode 100644 index 0000000..ab154d7 --- /dev/null +++ b/gamestream-webtransport-proxy/src/server.rs @@ -0,0 +1,16 @@ +use anyhow::Result; + +use crate::state::StateFile; + +pub struct Server { + pub state: StateFile, +} + + +impl Server { + pub fn new() -> Result { + Ok(Server { + state: StateFile::new()? + }) + } +} diff --git a/gamestream-webtransport-proxy/src/state.rs b/gamestream-webtransport-proxy/src/state.rs new file mode 100644 index 0000000..f837b7e --- /dev/null +++ b/gamestream-webtransport-proxy/src/state.rs @@ -0,0 +1,158 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + path::PathBuf, +}; + +use anyhow::{Result, anyhow}; +use serde::{Deserialize, Serialize}; +use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Serialize, Deserialize)] +pub struct GamestreamServer { + pub name: String, + pub host: String, + pub base_port: u16, +} + +impl GamestreamServer { + pub fn http_port(&self) -> u16 { + self.base_port + 189 + } + pub fn https_port(&self) -> u16 { + self.base_port + 184 + } +} + +#[derive(Serialize, Deserialize)] +struct State { + servers: HashMap, + unique_id: String, +} + +pub struct StateFile { + lock: tokio::sync::RwLock<()>, + path: PathBuf, +} + +pub trait StateReader { + fn servers(&self) -> Result>; + fn unique_id(&self) -> Result; +} + +pub trait StateWriter { + fn add_server(&self, server: GamestreamServer) -> Result<()>; +} + +pub struct StateReadAccess<'a> { + _guard: RwLockReadGuard<'a, ()>, + state: &'a StateFile, +} + +pub struct StateWriteAccess<'a> { + _guard: RwLockWriteGuard<'a, ()>, + state: &'a StateFile, +} + +impl StateFile { + fn load_state(&self) -> Result { + tracing::debug!("parsing state file"); + + let state_file = File::open(&self.path)?; + Ok(serde_json::from_reader(state_file)?) + } + + fn write_state(&self, state: State) -> Result<()> { + tracing::debug!("serializing state file"); + let state_file = File::create(&self.path)?; + Ok(serde_json::to_writer_pretty(state_file, &state)?) + } + + pub fn new() -> Result { + let project_dirs = + directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy") + .ok_or(anyhow::anyhow!("Could not get project dirs"))?; + + let data_dir = project_dirs.data_dir(); + fs::create_dir_all(data_dir)?; + + let state_path = data_dir.join("state.json"); + + if let Err(e) = File::open(&state_path) { + if e.kind() == std::io::ErrorKind::NotFound { + write_default_state_to_path(&state_path)?; + } else { + return Err(anyhow!(e)); + } + } + + Ok(StateFile { + lock: tokio::sync::RwLock::new(()), + path: state_path, + }) + } +} + +impl StateFile { + pub async fn read(&self) -> StateReadAccess { + StateReadAccess { + _guard: self.lock.read().await, + state: self, + } + } + + pub async fn write(&self) -> StateWriteAccess { + StateWriteAccess { + _guard: self.lock.write().await, + state: self, + } + } +} + +impl<'a> StateReader for StateReadAccess<'a> { + fn servers(&self) -> Result> { + let state = self.state.load_state()?; + Ok(state.servers) + } + + fn unique_id(&self) -> Result { + let state = self.state.load_state()?; + Ok(state.unique_id) + } +} + +impl<'a> StateWriter for StateWriteAccess<'a> { + fn add_server(&self, server: GamestreamServer) -> Result<()> { + let mut state = self.state.load_state()?; + + if state.servers.contains_key(&server.name) { + return Err(anyhow!( + "cannot add duplicate server with name: {}", + server.name + )); + } + + state.servers.insert(server.name.clone(), server); + + self.state.write_state(state)?; + + Ok(()) + } +} + +fn get_unique_id() -> Result { + let mut bytes = [0u8; 8]; + openssl::rand::rand_bytes(&mut bytes)?; + Ok(hex::encode(bytes)) +} + +fn write_default_state_to_path(path: &PathBuf) -> Result<()> { + let default_state = State { + servers: HashMap::new(), + unique_id: get_unique_id()?, + }; + + let state_file = File::create(path)?; + + Ok(serde_json::to_writer_pretty(state_file, &default_state)?) +}