#![allow(unused_imports)]
#![allow(unused_variables)]
use core::net::{Ipv4Addr, SocketAddr};
use bevy::prelude::*;
use core::time::Duration;
use ron;
use crate::shared::SharedSettings;
#[cfg(not(target_family = "wasm"))]
use async_compat::Compat;
use bevy::ecs::lifecycle::HookContext;
use bevy::ecs::world::DeferredWorld;
#[cfg(not(target_family = "wasm"))]
use bevy::tasks::IoTaskPool;
use lightyear::netcode::{NetcodeServer, PRIVATE_KEY_BYTES};
use lightyear::prelude::server::*;
use lightyear::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::warn;
#[cfg(target_family = "wasm")]
#[allow(unreachable_patterns)]
pub fn modify_digest_on_wasm(client_settings: &mut ClientSettings) -> Option<String> {
if let Some(new_digest) = get_digest_on_wasm() {
match &client_settings.transport {
ClientTransports::WebTransport { certificate_digest } => {
client_settings.transport = ClientTransports::WebTransport {
certificate_digest: new_digest.clone(),
};
Some(new_digest)
}
_ => None,
}
} else {
None
}
}
#[cfg(target_family = "wasm")]
pub fn get_digest_on_wasm() -> Option<String> {
let window = web_sys::window().expect("expected window");
if let Ok(obj) = window.location().hash() {
info!("Using cert digest from window.location().hash()");
let cd = obj.replace("#", "");
if cd.len() > 10 {
return Some(cd);
}
}
if let Some(obj) = window.get("CERT_DIGEST") {
info!("Using cert digest from window.CERT_DIGEST");
return Some(obj.as_string().expect("CERT_DIGEST should be a string"));
}
None
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ServerTransports {
#[cfg(feature = "udp")]
Udp {
local_port: u16,
},
WebTransport {
local_port: u16,
certificate: WebTransportCertificateSettings,
},
WebSocket {
local_port: u16,
},
#[cfg(feature = "steam")]
Steam {
local_port: u16,
},
}
#[derive(Component, Debug)]
#[component(on_add = ExampleServer::on_add)]
pub struct ExampleServer {
pub conditioner: Option<RecvLinkConditioner>,
pub transport: ServerTransports,
pub shared: SharedSettings,
}
impl ExampleServer {
fn on_add(mut world: DeferredWorld, context: HookContext) {
let entity = context.entity;
world.commands().queue(move |world: &mut World| -> Result {
let mut entity_mut = world.entity_mut(entity);
let settings = entity_mut.take::<ExampleServer>().unwrap();
entity_mut.insert((Name::from("Server"),));
let add_netcode = |entity_mut: &mut EntityWorldMut| {
let private_key = if let Some(key) = parse_private_key_from_env() {
info!("Using private key from LIGHTYEAR_PRIVATE_KEY env var");
key
} else {
settings.shared.private_key
};
entity_mut.insert(NetcodeServer::new(NetcodeConfig {
protocol_id: settings.shared.protocol_id,
private_key,
..Default::default()
}));
};
match settings.transport {
#[cfg(feature = "udp")]
ServerTransports::Udp { local_port } => {
add_netcode(&mut entity_mut);
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), local_port);
entity_mut.insert((LocalAddr(server_addr), ServerUdpIo::default()));
}
ServerTransports::WebTransport {
local_port,
certificate,
} => {
add_netcode(&mut entity_mut);
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), local_port);
entity_mut.insert((
LocalAddr(server_addr),
WebTransportServerIo {
certificate: (&certificate).into(),
},
));
}
ServerTransports::WebSocket { local_port } => {
add_netcode(&mut entity_mut);
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), local_port);
let sans = vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
"::1".to_string(),
];
let config = ServerConfig::builder()
.with_bind_address(server_addr)
.with_identity(
lightyear::websocket::server::Identity::self_signed(sans).unwrap(),
);
entity_mut.insert((LocalAddr(server_addr), WebSocketServerIo { config }));
}
#[cfg(feature = "steam")]
ServerTransports::Steam { local_port } => {
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), local_port);
entity_mut.insert(SteamServerIo {
target: ListenTarget::Addr(server_addr),
config: SessionConfig::default(),
});
}
};
Ok(())
});
}
}
pub(crate) fn start(mut commands: Commands, server: Single<Entity, With<Server>>) {
commands.trigger(Start {
entity: server.into_inner(),
});
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum WebTransportCertificateSettings {
AutoSelfSigned(Vec<String>),
FromFile {
cert: String,
key: String,
},
}
impl Default for WebTransportCertificateSettings {
fn default() -> Self {
let sans = vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
"::1".to_string(),
];
WebTransportCertificateSettings::AutoSelfSigned(sans)
}
}
impl From<&WebTransportCertificateSettings> for Identity {
fn from(wt: &WebTransportCertificateSettings) -> Identity {
match wt {
WebTransportCertificateSettings::AutoSelfSigned(sans) => {
let mut sans = sans.clone();
if let Ok(public_ip) = std::env::var("ARBITRIUM_PUBLIC_IP") {
println!("🔐 SAN += ARBITRIUM_PUBLIC_IP: {public_ip}");
sans.push(public_ip);
sans.push("*.pr.edgegap.net".to_string());
}
if let Ok(san) = std::env::var("SELF_SIGNED_SANS") {
println!("🔐 SAN += SELF_SIGNED_SANS: {san}");
sans.extend(san.split(',').map(|s| s.to_string()));
}
println!("🔐 Generating self-signed certificate with SANs: {sans:?}");
let identity = Identity::self_signed(sans).unwrap();
let digest = identity.certificate_chain().as_slice()[0].hash();
println!("🔐 Certificate digest: {digest}");
identity
}
WebTransportCertificateSettings::FromFile {
cert: cert_pem_path,
key: private_key_pem_path,
} => {
println!(
"Reading certificate PEM files:\n * cert: {cert_pem_path}\n * key: {private_key_pem_path}",
);
let identity = IoTaskPool::get()
.scope(|s| {
s.spawn(Compat::new(async {
Identity::load_pemfiles(cert_pem_path, private_key_pem_path)
.await
.unwrap()
}));
})
.pop()
.unwrap();
let digest = identity.certificate_chain().as_slice()[0].hash();
println!("🔐 Certificate digest: {digest}");
identity
}
}
}
}
pub fn parse_private_key_from_env() -> Option<[u8; PRIVATE_KEY_BYTES]> {
let Ok(key_str) = std::env::var("LIGHTYEAR_PRIVATE_KEY") else {
return None;
};
let private_key: Vec<u8> = key_str
.chars()
.filter(|c| c.is_ascii_digit() || *c == ',')
.collect::<String>()
.split(',')
.map(|s| {
s.parse::<u8>()
.expect("Failed to parse number in private key")
})
.collect();
if private_key.len() != PRIVATE_KEY_BYTES {
panic!("Private key must contain exactly {PRIVATE_KEY_BYTES} numbers",);
}
let mut bytes = [0u8; PRIVATE_KEY_BYTES];
bytes.copy_from_slice(&private_key);
Some(bytes)
}