use base64::{ alphabet::URL_SAFE, engine::{general_purpose::NO_PAD, GeneralPurpose}, Engine, }; use bevy::ecs::component::Component; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::{ collections::HashMap, error::Error, fmt::{self, Display}, }; use uuid::Uuid; pub(crate) const BASE64: GeneralPurpose = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase", untagged)] pub enum Message { Update(UpdateMessage), Ping(PingMessage), Sync(SyncMessage), RadioUpdate(RadioUpdateMessage), ServerSettings(ServerSettingsMessage), ClientDisconnect(ClientDisconnectMessage), VersionMismatch(VersionMismatchMessage), } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase", untagged)] pub enum MessageRequest { Update(UpdateMessage), Sync(SyncMessageRequest), RadioUpdate(RadioUpdateMessage), } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateMessage { pub msg_type: MsgType<0>, pub client: Client, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct PingMessage { pub msg_type: MsgType<1>, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SyncMessage { pub msg_type: MsgType<2>, pub clients: Vec, pub server_settings: HashMap, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SyncMessageRequest { pub msg_type: MsgType<2>, pub client: Client, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct RadioUpdateMessage { pub msg_type: MsgType<3>, pub client: Client, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ServerSettingsMessage { pub msg_type: MsgType<4>, pub server_settings: HashMap, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ClientDisconnectMessage { pub msg_type: MsgType<5>, pub client: Client, pub version: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct VersionMismatchMessage { pub msg_type: MsgType<6>, pub version: String, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Coalition { Spectator, Blue, Red, } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Radio { pub enc: bool, pub enc_key: u8, pub freq: f64, pub modulation: Modulation, pub sec_freq: f64, pub retransmit: bool, } impl Default for Radio { fn default() -> Self { Radio { enc: false, enc_key: 0, freq: 1.0, modulation: Modulation::Disabled, sec_freq: 1.0, retransmit: false, } } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum Modulation { Am = 0, Fm = 1, Intercom = 2, #[default] Disabled = 3, HaveQuick = 4, Satcom = 5, Mids = 6, } #[derive(Component, Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RadioInfo { pub radios: Vec, pub unit: String, pub unit_id: u32, pub iff: Transponder, } #[derive(Debug, PartialEq, Default, Clone, Copy, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum RadioSwitchControls { #[default] Hotas = 0, InCockpit = 1, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Client { pub client_guid: String, pub name: String, pub seat: u32, pub coalition: Coalition, pub allow_record: bool, #[serde(skip_serializing_if = "Option::is_none")] pub radio_info: Option, pub lat_lng_position: Position, } #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] pub struct Position { pub lat: f64, #[serde(rename = "lng")] pub lon: f64, pub alt: f64, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Transponder { control: IffControlMode, mode1: i32, mode3: i32, mode4: bool, mic: i32, status: IffStatus, } #[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum IffControlMode { Cockpit = 0, Overlay = 1, Disabled = 2, } #[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum IffStatus { Off = 0, Normal = 1, Ident = 2, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct MsgType; #[derive(Debug)] struct MsgTypeError; impl Error for MsgTypeError {} impl Display for MsgTypeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Invalid message type") } } impl Serialize for MsgType { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_u8(V) } } impl<'de, const V: u8> Deserialize<'de> for MsgType { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let value = u8::deserialize(deserializer)?; if value == V { Ok(MsgType::) } else { Err(serde::de::Error::custom(MsgTypeError)) } } } impl Default for Transponder { fn default() -> Self { Transponder { control: IffControlMode::Disabled, mode1: -1, mode3: -1, mode4: false, mic: -1, status: IffStatus::Off, } } } impl ::serde::Serialize for Coalition { fn serialize(&self, serializer: S) -> Result where S: ::serde::Serializer, { // Serialize the enum as a u64. serializer.serialize_u64(match *self { Coalition::Spectator => 0, Coalition::Red => 1, Coalition::Blue => 2, }) } } impl<'de> ::serde::Deserialize<'de> for Coalition { fn deserialize(deserializer: D) -> Result where D: ::serde::Deserializer<'de>, { struct Visitor; impl<'de> ::serde::de::Visitor<'de> for Visitor { type Value = Coalition; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("positive integer or string") } fn visit_u64(self, value: u64) -> Result where E: ::serde::de::Error, { // Rust does not come with a simple way of converting a // number to an enum, so use a big `match`. match value { 0 => Ok(Coalition::Spectator), 1 => Ok(Coalition::Red), 2 => Ok(Coalition::Blue), _ => Err(E::custom(format!( "unknown {} value: {}", stringify!(Coalition), value ))), } } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { let value = value.to_lowercase().clone(); let value = value.as_str(); match value { "spectator" => Ok(Coalition::Spectator), "red" => Ok(Coalition::Red), "blue" => Ok(Coalition::Blue), _ => Err(E::custom(format!( "unknown {} value: {}", stringify!(Coalition), value ))), } } } // Deserialize the enum from a u64. deserializer.deserialize_u64(Visitor) } } pub fn create_sguid() -> String { let sguid = Uuid::new_v4(); // let sguid = base64::encode_config(sguid.as_bytes(), base64::URL_SAFE_NO_PAD); let sguid = BASE64.encode(sguid.as_bytes()); assert_eq!(sguid.len(), 22); sguid }