#![allow(clippy::ptr_arg, reason = "`&mut Vec` needed when certain features are enabled")]
use crate::common::{ConnectMetaNative, ConnectMetaWasmWs, ConnectMetaWasmWt, ConnectMetas, GameServerSetupConfig};
use renet2::{ConnectionConfig, RenetServer};
use renet2_netcode::{BoxedSocket, NetcodeServerTransport, ServerAuthentication, ServerSetupConfig};
use std::net::SocketAddr;
use wasm_timer::{SystemTime, UNIX_EPOCH};
use super::ClientCounts;
#[cfg(feature = "ws_server_transport")]
fn make_websocket_url(with_tls: bool, ip: std::net::IpAddr, port: u16, maybe_domain: Option<String>) -> Result<url::Url, url::ParseError> {
let mut url = url::Url::parse("https://example.net")?;
let scheme = match with_tls {
true => "wss",
false => "ws",
};
url.set_scheme(scheme).map_err(|_| url::ParseError::EmptyHost)?;
match maybe_domain {
Some(domain) => url.set_host(Some(domain.as_str()))?,
None => url.set_ip_host(ip).map_err(|_| url::ParseError::InvalidIpv4Address)?,
}
url.set_port(Some(port)).map_err(|_| url::ParseError::InvalidPort)?;
Ok(url)
}
#[allow(unused_variables)]
fn add_memory_socket(
config: &GameServerSetupConfig,
memory_clients: Vec<u16>,
socket_addresses: &mut Vec<Vec<SocketAddr>>,
sockets: &mut Vec<BoxedSocket>,
auth_key: &[u8; 32],
) -> Result<Option<crate::ConnectMetaMemory>, String> {
if memory_clients.is_empty() {
return Ok(None);
}
#[cfg(not(feature = "memory_transport"))]
{
Err(
"tried setting up renet2 server with in-memory clients, but memory_transport feature \
is not enabled"
.to_string(),
)
}
#[cfg(feature = "memory_transport")]
{
let (server_socket, client_sockets) = renet2_netcode::new_memory_sockets(memory_clients, true, true);
let addrs = vec![renet2_netcode::in_memory_server_addr()];
let meta = crate::ConnectMetaMemory {
server_config: config.clone(),
clients: client_sockets,
socket_id: sockets.len() as u8, auth_key: *auth_key,
};
socket_addresses.push(addrs);
sockets.push(BoxedSocket::new(server_socket));
Ok(Some(meta))
}
}
#[allow(unused_variables)]
fn add_native_socket(
config: &GameServerSetupConfig,
native_count: usize,
socket_addresses: &mut Vec<Vec<SocketAddr>>,
sockets: &mut Vec<BoxedSocket>,
auth_key: &[u8; 32],
) -> Result<Option<ConnectMetaNative>, String> {
if native_count == 0 {
return Ok(None);
}
#[cfg(not(feature = "native_transport"))]
{
Err("tried setting up renet2 server with native clients, but native_transport feature \
is not enabled"
.to_string())
}
#[cfg(feature = "native_transport")]
{
use renet2_netcode::ServerSocket;
let wildcard_addr = SocketAddr::new(config.server_ip, config.native_port);
let server_socket = std::net::UdpSocket::bind(wildcard_addr)
.map_err(|err| format!("failed binding renet2 server address {wildcard_addr:?}: {err:?}"))?;
let socket =
renet2_netcode::NativeSocket::new(server_socket).map_err(|err| format!("failed constructing renet2 native socket: {err:?}"))?;
let local_addr = socket
.addr()
.map_err(|err| format!("failed getting local addr for renet2 native socket: {err:?}"))?;
let public_port = if config.native_port_proxy > 0 { config.native_port_proxy } else { local_addr.port() };
let addrs = if let Some(proxy) = config.proxy_ip { vec![SocketAddr::new(proxy, public_port)] } else { vec![local_addr] };
let meta = ConnectMetaNative {
server_config: config.clone(),
server_addresses: addrs.clone(),
socket_id: sockets.len() as u8, auth_key: *auth_key,
};
log::info!("native renet2 socket; local addr = {}, public addr = {}", local_addr, addrs[0]);
socket_addresses.push(addrs);
sockets.push(BoxedSocket::new(socket));
Ok(Some(meta))
}
}
#[allow(unused_variables)]
fn add_wasm_wt_socket(
config: &GameServerSetupConfig,
count: usize,
socket_addresses: &mut Vec<Vec<SocketAddr>>,
sockets: &mut Vec<BoxedSocket>,
auth_key: &[u8; 32],
) -> Result<Option<ConnectMetaWasmWt>, String> {
if count == 0 {
return Ok(None);
}
#[cfg(not(feature = "wt_server_transport"))]
{
Err("tried setting up renet2 server with wasm webtransport clients, but \
wt_server_transport feature is not enabled"
.to_string())
}
#[cfg(feature = "wt_server_transport")]
{
use enfync::AdoptOrDefault;
use renet2_netcode::ServerSocket;
let wildcard_addr = SocketAddr::new(config.server_ip, config.wasm_wt_port);
let (wt_config, cert_hash) = renet2_netcode::WebTransportServerConfig::new_selfsigned(wildcard_addr, count)
.map_err(|err| format!("failed constructing renet2 webtransport socket config: {err:?}"))?;
let handle = enfync::builtin::native::TokioHandle::adopt_or_default(); let socket = renet2_netcode::WebTransportServer::new(wt_config, handle.0)
.map_err(|err| format!("failed constructing renet2 webtransport socket: {err:?}"))?;
let local_addr = socket
.addr()
.map_err(|err| format!("failed getting local addr for renet2 webtransport socket: {err:?}"))?;
let public_port = if config.wasm_wt_port_proxy > 0 { config.wasm_wt_port_proxy } else { local_addr.port() };
let addrs = if let Some(proxy) = config.proxy_ip { vec![SocketAddr::new(proxy, public_port)] } else { vec![local_addr] };
let meta = ConnectMetaWasmWt {
server_config: config.clone(),
server_addresses: addrs.clone(),
socket_id: sockets.len() as u8, auth_key: *auth_key,
cert_hashes: vec![cert_hash],
};
log::info!(
"wasm webtransport renet2 socket; local addr = {}, public addr = {}",
local_addr,
addrs[0]
);
socket_addresses.push(addrs);
sockets.push(BoxedSocket::new(socket));
Ok(Some(meta))
}
}
#[allow(unused_variables)]
fn add_wasm_ws_socket(
config: &GameServerSetupConfig,
count: usize,
socket_addresses: &mut Vec<Vec<SocketAddr>>,
sockets: &mut Vec<BoxedSocket>,
auth_key: &[u8; 32],
) -> Result<Option<ConnectMetaWasmWs>, String> {
if count == 0 {
return Ok(None);
}
#[cfg(not(feature = "ws_server_transport"))]
{
Err(
"tried setting up renet2 server with wasm websocket clients, but ws_server_transport \
feature is not enabled"
.to_string(),
)
}
#[cfg(feature = "ws_server_transport")]
{
use enfync::AdoptOrDefault;
use renet2_netcode::ServerSocket;
let acceptor = config.get_ws_acceptor()?;
let listen = SocketAddr::new(config.server_ip, config.wasm_ws_port);
let ws_config = renet2_netcode::WebSocketServerConfig {
acceptor,
listen,
max_clients: count,
};
let handle = enfync::builtin::native::TokioHandle::adopt_or_default(); let socket = renet2_netcode::WebSocketServer::new(ws_config, handle.0)
.map_err(|err| format!("failed constructing renet2 websocket socket: {err:?}"))?;
let local_addr = socket
.addr()
.map_err(|err| format!("failed getting local addr for renet2 native socket: {err:?}"))?;
let public_port = if config.wasm_ws_port_proxy > 0 { config.wasm_ws_port_proxy } else { local_addr.port() };
let addrs = if config.ws_domain.is_some() {
vec![SocketAddr::from(([0, 0, 0, 0], 0))]
} else if let Some(proxy) = config.proxy_ip {
vec![SocketAddr::new(proxy, public_port)]
} else {
vec![local_addr]
};
let url = make_websocket_url(socket.is_encrypted(), addrs[0].ip(), public_port, config.ws_domain.clone())
.map_err(|err| format!("failed constructing renet2 websocket url: {err:?}"))?;
log::info!("wasm websockets renet2 socket; local addr = {}, url = {}", local_addr, url);
let meta = ConnectMetaWasmWs {
server_config: config.clone(),
server_addresses: addrs.clone(),
socket_id: sockets.len() as u8, auth_key: *auth_key,
url,
};
socket_addresses.push(addrs);
sockets.push(BoxedSocket::new(socket));
Ok(Some(meta))
}
}
#[cfg(feature = "native_transport")]
fn _create_native_server(
connection_config: ConnectionConfig,
mut server_config: ServerSetupConfig,
) -> Result<(RenetServer, NetcodeServerTransport), String> {
let server = RenetServer::new(connection_config);
let server_socket = std::net::UdpSocket::bind(server_config.socket_addresses[0][0]).map_err(|err| {
format!(
"failed binding renet2 server address {:?}: {err:?}",
server_config.socket_addresses[0][0]
)
})?;
let local_addr = server_socket
.local_addr()
.map_err(|err| format!("failed getting local addr for renet2 native socket: {err:?}"))?;
server_config.socket_addresses = vec![vec![local_addr]];
let server_transport = NetcodeServerTransport::new(
server_config,
renet2_netcode::NativeSocket::new(server_socket).map_err(|err| format!("failed constructing renet2 native socket: {err:?}"))?,
)
.map_err(|err| format!("failed constructing renet2 netcode server transport: {err:?}"))?;
Ok((server, server_transport))
}
#[cfg(all(feature = "bevy", feature = "native_transport"))]
pub fn setup_native_renet_server_in_bevy(
server_world: &mut bevy_ecs::prelude::World,
server_config: ServerSetupConfig,
connection_config: ConnectionConfig,
) -> Result<SocketAddr, String> {
log::info!("setting up renet2 server");
let (server, server_transport) = _create_native_server(connection_config, server_config)?;
let server_addr = server_transport.addresses()[0];
server_world.insert_resource(server);
server_world.insert_resource(server_transport);
Ok(server_addr)
}
pub fn setup_combo_renet2_server_with_key(
config: GameServerSetupConfig,
counts: ClientCounts,
connection_config: ConnectionConfig,
auth_key: &[u8; 32],
) -> Result<(RenetServer, NetcodeServerTransport, ConnectMetas), String> {
log::info!("setting up renet2 server");
let max_clients = counts.total();
let mut socket_addresses = Vec::default();
let mut sockets = Vec::default();
let memory_meta = add_memory_socket(&config, counts.memory_clients, &mut socket_addresses, &mut sockets, auth_key)?;
let native_meta = add_native_socket(&config, counts.native_count, &mut socket_addresses, &mut sockets, auth_key)?;
let wasm_wt_meta = add_wasm_wt_socket(&config, counts.wasm_wt_count, &mut socket_addresses, &mut sockets, auth_key)?;
let wasm_ws_meta = add_wasm_ws_socket(&config, counts.wasm_ws_count, &mut socket_addresses, &mut sockets, auth_key)?;
let connect_metas = ConnectMetas {
memory: memory_meta,
native: native_meta,
wasm_wt: wasm_wt_meta,
wasm_ws: wasm_ws_meta,
};
let server_config = ServerSetupConfig {
current_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default(),
max_clients,
protocol_id: config.protocol_id,
socket_addresses,
authentication: ServerAuthentication::Secure { private_key: *auth_key },
};
let server = RenetServer::new(connection_config);
let server_transport = NetcodeServerTransport::new_with_sockets(server_config, sockets)
.map_err(|err| format!("failed constructing renet2 netcode server transport: {err:?}"))?;
Ok((server, server_transport, connect_metas))
}
pub fn setup_combo_renet2_server(
config: GameServerSetupConfig,
client_counts: ClientCounts,
connection_config: ConnectionConfig,
) -> Result<(RenetServer, NetcodeServerTransport, ConnectMetas), String> {
let auth_key: [u8; 32] = {
#[cfg(target_family = "wasm")]
{
let wasm_count = client_counts.wasm_wt_count + client_counts.wasm_ws_count;
if client_counts.native_count > 0 || wasm_count > 0 {
return Err(format!(
"aborting game app networking construction; target family is WASM where only in-memory \
transports are permitted, but found other transport requests (memory: {:?}, native: {:?}, wasm: {:?})",
client_counts.memory_clients, client_counts.native_count, wasm_count
));
}
let time: [u8; 16] = wasm_timer::SystemTime::now()
.duration_since(wasm_timer::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
.to_le_bytes();
let mut key: [u8; 32] = Default::default();
key[..16].clone_from_slice(&time);
key
}
#[cfg(not(target_family = "wasm"))]
renet2_netcode::generate_random_bytes::<32>()
};
setup_combo_renet2_server_with_key(config, client_counts, connection_config, &auth_key)
}
#[cfg(feature = "bevy")]
pub fn setup_combo_renet2_server_in_bevy_with_key(
server_world: &mut bevy_ecs::prelude::World,
config: GameServerSetupConfig,
counts: ClientCounts,
auth_key: &[u8; 32],
connection_config: ConnectionConfig,
) -> Result<ConnectMetas, String> {
let (server, server_transport, connect_metas) = setup_combo_renet2_server_with_key(config, counts, connection_config, auth_key)?;
server_world.insert_resource(server);
server_world.insert_resource(server_transport);
Ok(connect_metas)
}
#[cfg(feature = "bevy")]
pub fn setup_combo_renet2_server_in_bevy(
server_world: &mut bevy_ecs::prelude::World,
config: GameServerSetupConfig,
counts: ClientCounts,
connection_config: ConnectionConfig,
) -> Result<ConnectMetas, String> {
let (server, server_transport, connect_metas) = setup_combo_renet2_server(config, counts, connection_config)?;
server_world.insert_resource(server);
server_world.insert_resource(server_transport);
Ok(connect_metas)
}