338 lines
8.5 KiB
Rust
Executable File
338 lines
8.5 KiB
Rust
Executable File
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<Client>,
|
|
pub server_settings: HashMap<String, String>,
|
|
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<String, String>,
|
|
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<Radio>,
|
|
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<RadioInfo>,
|
|
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<const V: u8>;
|
|
|
|
#[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<const V: u8> Serialize for MsgType<V> {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_u8(V)
|
|
}
|
|
}
|
|
|
|
impl<'de, const V: u8> Deserialize<'de> for MsgType<V> {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let value = u8::deserialize(deserializer)?;
|
|
if value == V {
|
|
Ok(MsgType::<V>)
|
|
} 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
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<D>(deserializer: D) -> Result<Self, D::Error>
|
|
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<E>(self, value: u64) -> Result<Coalition, E>
|
|
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<E>(self, value: &str) -> Result<Self::Value, E>
|
|
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
|
|
}
|