use std::net::Ipv4Addr;
use std::{fmt::Display, str::FromStr};
use crate::messages::*;
use crate::structs::{RadioFrequency, TransponderCode};
use crate::{aircraft_config::AircraftConfig, errors::FsdMessageParseError};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ClientCapability {
Version,
ATCInfo,
ModelDesc,
ACConfig,
VisUpdate,
RadarUpdate,
ATCMulti,
SecPos,
IcaoEq,
FastPos,
OngoingCoord,
InterimPos,
Stealth,
Teamspeak,
NewATIS,
NewInfo,
Mumble,
GlobalData,
Estimates,
Simulated,
ObsPilot,
Custom(String),
}
impl ClientCapability {
pub fn is_known(&self) -> bool {
return !matches!(self, ClientCapability::Custom(_));
}
}
impl<S: AsRef<str>> From<S> for ClientCapability {
fn from(value: S) -> Self {
let value = value.as_ref().to_uppercase();
match value.as_str() {
"VERSION" => ClientCapability::Version,
"ATCINFO" => ClientCapability::ATCInfo,
"MODELDESC" => ClientCapability::ModelDesc,
"ACCONFIG" => ClientCapability::ACConfig,
"VISUPDATE" => ClientCapability::VisUpdate,
"RADARUPDATE" => ClientCapability::RadarUpdate,
"ATCMULTI" => ClientCapability::ATCMulti,
"SECPOS" => ClientCapability::SecPos,
"ICAOEQ" => ClientCapability::IcaoEq,
"FASTPOS" => ClientCapability::FastPos,
"ONGOINGCOORD" => ClientCapability::OngoingCoord,
"INTERIMPOS" => ClientCapability::InterimPos,
"STEALTH" => ClientCapability::Stealth,
"TEAMSPEAK" => ClientCapability::Teamspeak,
"NEWATIS" => ClientCapability::NewATIS,
"NEWINFO" => ClientCapability::NewInfo,
"MUMBLE" => ClientCapability::Mumble,
"GLOBALDATA" => ClientCapability::GlobalData,
"ESTIMATES" => ClientCapability::Estimates,
"SIMULATED" => ClientCapability::Simulated,
"OBSPILOT" => ClientCapability::ObsPilot,
_ => ClientCapability::Custom(value),
}
}
}
impl Display for ClientCapability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientCapability::ACConfig => write!(f, "ACCONFIG"),
ClientCapability::ATCInfo => write!(f, "ATCINFO"),
ClientCapability::ModelDesc => write!(f, "MODELDESC"),
ClientCapability::Version => write!(f, "VERSION"),
ClientCapability::VisUpdate => write!(f, "VISUPDATE"),
ClientCapability::RadarUpdate => write!(f, "RADARUPDATE"),
ClientCapability::ATCMulti => write!(f, "ATCMULTI"),
ClientCapability::SecPos => write!(f, "SECPOS"),
ClientCapability::IcaoEq => write!(f, "ICAOEQ"),
ClientCapability::FastPos => write!(f, "FASTPOS"),
ClientCapability::OngoingCoord => write!(f, "ONGOINGCOORD"),
ClientCapability::InterimPos => write!(f, "INTERIMPOS"),
ClientCapability::Stealth => write!(f, "STEALTH"),
ClientCapability::Teamspeak => write!(f, "TEAMSPEAK"),
ClientCapability::NewATIS => write!(f, "NEWATIS"),
ClientCapability::NewInfo => write!(f, "NEWINFO"),
ClientCapability::Mumble => write!(f, "MUMBLE"),
ClientCapability::GlobalData => write!(f, "GLOBALDATA"),
ClientCapability::Estimates => write!(f, "ESTIMATES"),
ClientCapability::Simulated => write!(f, "SIMULATED"),
ClientCapability::ObsPilot => write!(f, "OBSPILOT"),
ClientCapability::Custom(value) => write!(f, "{value}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtcRating {
Observer = 1,
S1,
S2,
S3,
C1,
C2,
C3,
I1,
I2,
I3,
Supervisor,
Administrator,
}
impl FromStr for AtcRating {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rating_u8 =
u8::from_str(s).map_err(|_| FsdMessageParseError::InvalidRating(s.to_string()))?;
match rating_u8 {
1 => Ok(AtcRating::Observer),
2 => Ok(AtcRating::S1),
3 => Ok(AtcRating::S2),
4 => Ok(AtcRating::S3),
5 => Ok(AtcRating::C1),
6 => Ok(AtcRating::C2),
7 => Ok(AtcRating::C3),
8 => Ok(AtcRating::I1),
9 => Ok(AtcRating::I2),
10 => Ok(AtcRating::I3),
11 => Ok(AtcRating::Supervisor),
12 => Ok(AtcRating::Administrator),
_ => Err(FsdMessageParseError::InvalidRating(s.to_string())),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum PilotRating {
Student = 1,
VFR,
IFR,
Instructor,
Supervisor,
}
impl FromStr for PilotRating {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rating_u8 =
u8::from_str(s).map_err(|_| FsdMessageParseError::InvalidRating(s.to_string()))?;
match rating_u8 {
1 => Ok(PilotRating::Student),
2 => Ok(PilotRating::VFR),
3 => Ok(PilotRating::IFR),
4 => Ok(PilotRating::Instructor),
5 => Ok(PilotRating::Supervisor),
_ => Err(FsdMessageParseError::InvalidRating(s.to_string())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtocolRevision {
Classic = 9,
VatsimNoAuth = 10,
VatsimAuth = 100,
Vatsim2022 = 101,
}
impl FromStr for ProtocolRevision {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"1" | "9" => Ok(Self::Classic),
"10" => Ok(Self::VatsimNoAuth),
"100" => Ok(Self::VatsimAuth),
"101" => Ok(Self::Vatsim2022),
_ => Err(FsdMessageParseError::InvalidProtocolRevison(s.to_string())),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SimulatorType {
Unknown,
MSFS95,
MSFS98,
MSCFS,
MSFS2000,
MSCFS2,
MSFS2002,
MSCFS3,
MSFS2004,
MSFSX,
MSFS,
MSFS2024,
XPLANE8,
XPLANE9,
XPLANE10,
XPLANE11,
XPLANE12,
P3Dv1,
P3Dv2,
P3Dv3,
P3Dv4,
P3Dv5,
FlightGear,
}
impl<S: AsRef<str>> From<S> for SimulatorType {
fn from(value: S) -> Self {
match value.as_ref() {
"1" => SimulatorType::MSFS95,
"2" => SimulatorType::MSFS98,
"3" => SimulatorType::MSCFS,
"4" => SimulatorType::MSFS2000,
"5" => SimulatorType::MSCFS2,
"6" => SimulatorType::MSFS2002,
"7" => SimulatorType::MSCFS3,
"8" => SimulatorType::MSFS2004,
"9" => SimulatorType::MSFSX,
"10" => SimulatorType::MSFS,
"11" => SimulatorType::MSFS2024,
"12" => SimulatorType::XPLANE8,
"13" => SimulatorType::XPLANE9,
"14" => SimulatorType::XPLANE10,
"15" => SimulatorType::XPLANE11,
"16" => SimulatorType::XPLANE12,
"17" => SimulatorType::P3Dv1,
"18" => SimulatorType::P3Dv2,
"19" => SimulatorType::P3Dv3,
"20" => SimulatorType::P3Dv4,
"21" => SimulatorType::P3Dv5,
"22" => SimulatorType::FlightGear,
_ => SimulatorType::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlightRules {
DVFR,
SVFR,
VFR,
IFR,
}
impl FromStr for FlightRules {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_uppercase();
match s.as_str() {
"D" => Ok(FlightRules::DVFR),
"S" => Ok(FlightRules::SVFR),
"V" => Ok(FlightRules::VFR),
"I" => Ok(FlightRules::IFR),
_ => Err(FsdMessageParseError::InvalidFlightRules(s)),
}
}
}
impl Display for FlightRules {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
FlightRules::DVFR => write!(f, "D"),
FlightRules::VFR => write!(f, "V"),
FlightRules::SVFR => write!(f, "S"),
FlightRules::IFR => write!(f, "I"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtcType {
Observer,
FlightServiceStation,
Delivery,
Ground,
Tower,
Approach,
Centre,
}
impl AtcType {
pub fn max_range(&self) -> u16 {
match *self {
AtcType::Observer => 300,
AtcType::FlightServiceStation => 1500,
AtcType::Delivery | AtcType::Ground => 20,
AtcType::Tower => 50,
AtcType::Approach => 150,
AtcType::Centre => 600
}
}
}
impl FromStr for AtcType {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"0" => Ok(AtcType::Observer),
"1" => Ok(AtcType::FlightServiceStation),
"2" => Ok(AtcType::Delivery),
"3" => Ok(AtcType::Ground),
"4" => Ok(AtcType::Tower),
"5" => Ok(AtcType::Approach),
"6" => Ok(AtcType::Centre),
_ => Err(FsdMessageParseError::InvalidAtcType(s.to_string())),
}
}
}
impl Display for AtcType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AtcType::Observer => f.write_str("Observer"),
AtcType::FlightServiceStation => {
f.write_str("Flight Service Station or Flow Management")
}
AtcType::Delivery => f.write_str("Delivery"),
AtcType::Ground => f.write_str("Ground, Ramp or Apron"),
AtcType::Tower => f.write_str("Tower"),
AtcType::Approach => f.write_str("Approach or Departure"),
AtcType::Centre => f.write_str("Centre"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransponderMode {
Standby,
ModeC,
Ident,
}
impl FromStr for TransponderMode {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"S" => Ok(TransponderMode::Standby),
"N" => Ok(TransponderMode::ModeC),
"Y" => Ok(TransponderMode::Ident),
_ => Err(FsdMessageParseError::InvalidTransponderMode(s.to_string())),
}
}
}
impl Display for TransponderMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Standby => write!(f, "S"),
Self::ModeC => write!(f, "N"),
Self::Ident => write!(f, "Y"),
}
}
}
#[derive(Clone, Debug)]
pub enum FsdMessageType {
AtcRegisterMessage(AtcRegisterMessage),
PilotRegisterMessage(PilotRegisterMessage),
AtcDeregisterMessage(AtcDeregisterMessage),
PilotDeregisterMessage(PilotDeregisterMessage),
AtcPositionUpdateMessage(AtcPositionUpdateMessage),
AtcSecondaryVisCentreMessage(AtcSecondaryVisCentreMessage),
PilotPositionUpdateMessage(PilotPositionUpdateMessage),
AuthenticationChallengeMessage(AuthenticationChallengeMessage),
AuthenticationResponseMessage(AuthenticationResponseMessage),
TextMessage(TextMessage),
FrequencyMessage(FrequencyMessage),
ChangeServerMessage(ChangeServerMessage),
InitialServerHandshakeMessage(InitialServerHandshakeMessage),
InitialClientHandshakeMessage(InitialClientHandshakeMessage),
SendFastPositionUpdatesMessage(SendFastPositionUpdatesMessage),
VelocityPositionStoppedMessage(VelocityPositionStoppedMessage),
VelocityPositionSlowMessage(VelocityPositionSlowMessage),
VelocityPositionFastMessage(VelocityPositionFastMessage),
KillMessage(KillMessage),
MetarRequestMessage(MetarRequestMessage),
MetarResponseMessage(MetarResponseMessage),
PingMessage(PingMessage),
PongMessage(PongMessage),
PlaneInfoRequestMessage(PlaneInfoRequestMessage),
PlaneInfoResponseMessage(PlaneInfoResponseMessage),
FsdErrorMessage(FsdErrorMessage),
FlightPlanMessage(FlightPlanMessage),
FlightPlanAmendmentMessage(FlightPlanAmendmentMessage),
FSInnPlaneInformationRequestMessage, FSInnPlaneInformationResponseMessage, ServerHeartbeat,
ClientQueryMessage(ClientQueryMessage),
ClientQueryResponseMessage(ClientQueryResponseMessage),
HandoffOfferMessage(HandoffOfferMessage),
HandoffAcceptMessage(HandoffAcceptMessage),
SharedStateMessage(SharedStateMessage),
}
impl FsdMessageType {
pub(crate) fn identify(message: &str) -> Result<FsdMessageType, FsdMessageParseError> {
let fields: Vec<&str> = message.split(':').collect();
if fields.len() < 2 {
if fields[0].starts_with("#DA") {
return Ok(Self::AtcDeregisterMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#DP") {
return Ok(Self::PilotDeregisterMessage(fields.as_slice().try_into()?));
}
return Err(FsdMessageParseError::UnknownMessageType(
message.to_string(),
));
}
if fields[0].starts_with("#DA") {
return Ok(Self::AtcDeregisterMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#DP") {
return Ok(Self::PilotDeregisterMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#AA") {
return Ok(Self::AtcRegisterMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#AP") {
return Ok(Self::PilotRegisterMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with('%') {
return Ok(Self::AtcPositionUpdateMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with('\'') {
return Ok(Self::AtcSecondaryVisCentreMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with('@') {
return Ok(Self::PilotPositionUpdateMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$ZC") {
return Ok(Self::AuthenticationChallengeMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$ZR") {
return Ok(Self::AuthenticationResponseMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$ER") {
return Ok(Self::FsdErrorMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$HO") {
return Ok(Self::HandoffOfferMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$HA") {
return Ok(Self::HandoffAcceptMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#TM") {
if fields[1].starts_with('@') {
return Ok(Self::FrequencyMessage(fields.as_slice().try_into()?));
}
return Ok(Self::TextMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$XX") {
return Ok(Self::ChangeServerMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$FP") {
return Ok(Self::FlightPlanMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$AM") {
return Ok(Self::FlightPlanAmendmentMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$DI") {
return Ok(Self::InitialServerHandshakeMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$ID") {
return Ok(Self::InitialClientHandshakeMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$SF") {
return Ok(Self::SendFastPositionUpdatesMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("#ST") {
return Ok(Self::VelocityPositionStoppedMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("#DL") {
return Ok(Self::ServerHeartbeat);
}
if fields[0].starts_with("#SL") {
return Ok(Self::VelocityPositionSlowMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("#PC") {
return Ok(Self::SharedStateMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with('^') {
return Ok(Self::VelocityPositionFastMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$!!") {
return Ok(Self::KillMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$AX") {
return Ok(Self::MetarRequestMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$AR") {
return Ok(Self::MetarResponseMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$CQ") {
return Ok(Self::ClientQueryMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$CR") {
return Ok(Self::ClientQueryResponseMessage(
fields.as_slice().try_into()?,
));
}
if fields[0].starts_with("$PI") {
return Ok(Self::PingMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("$PO") {
return Ok(Self::PongMessage(fields.as_slice().try_into()?));
}
if fields[0].starts_with("#SB") {
if fields[2] == "PIR" {
return Ok(Self::PlaneInfoRequestMessage(fields.as_slice().try_into()?));
}
if fields[2] == "PI" {
return Ok(Self::PlaneInfoResponseMessage(
fields.as_slice().try_into()?,
));
}
if fields[2] == "FSIPI" {
return Ok(Self::FSInnPlaneInformationResponseMessage);
}
if fields[2] == "FSIPIR" {
return Ok(Self::FSInnPlaneInformationRequestMessage);
}
}
Err(FsdMessageParseError::UnknownMessageType(
message.to_string(),
))
}
}
impl Display for FsdMessageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FsdMessageType::AtcRegisterMessage(m) => m.fmt(f),
FsdMessageType::PilotRegisterMessage(m) => m.fmt(f),
FsdMessageType::AtcDeregisterMessage(m) => m.fmt(f),
FsdMessageType::PilotDeregisterMessage(m) => m.fmt(f),
FsdMessageType::AtcPositionUpdateMessage(m) => m.fmt(f),
FsdMessageType::AtcSecondaryVisCentreMessage(m) => m.fmt(f),
FsdMessageType::PilotPositionUpdateMessage(m) => m.fmt(f),
FsdMessageType::AuthenticationChallengeMessage(m) => m.fmt(f),
FsdMessageType::AuthenticationResponseMessage(m) => m.fmt(f),
FsdMessageType::TextMessage(m) => m.fmt(f),
FsdMessageType::FrequencyMessage(m) => m.fmt(f),
FsdMessageType::ChangeServerMessage(m) => m.fmt(f),
FsdMessageType::InitialServerHandshakeMessage(m) => m.fmt(f),
FsdMessageType::InitialClientHandshakeMessage(m) => m.fmt(f),
FsdMessageType::SendFastPositionUpdatesMessage(m) => m.fmt(f),
FsdMessageType::VelocityPositionStoppedMessage(m) => m.fmt(f),
FsdMessageType::VelocityPositionSlowMessage(m) => m.fmt(f),
FsdMessageType::VelocityPositionFastMessage(m) => m.fmt(f),
FsdMessageType::KillMessage(m) => m.fmt(f),
FsdMessageType::MetarRequestMessage(m) => m.fmt(f),
FsdMessageType::MetarResponseMessage(m) => m.fmt(f),
FsdMessageType::PingMessage(m) => m.fmt(f),
FsdMessageType::PongMessage(m) => m.fmt(f),
FsdMessageType::PlaneInfoRequestMessage(m) => m.fmt(f),
FsdMessageType::PlaneInfoResponseMessage(m) => m.fmt(f),
FsdMessageType::FsdErrorMessage(m) => m.fmt(f),
FsdMessageType::FlightPlanMessage(m) => m.fmt(f),
FsdMessageType::FlightPlanAmendmentMessage(m) => m.fmt(f),
m @ FsdMessageType::FSInnPlaneInformationRequestMessage => m.fmt(f),
m @ FsdMessageType::FSInnPlaneInformationResponseMessage => m.fmt(f),
m @ FsdMessageType::ServerHeartbeat => m.fmt(f),
FsdMessageType::ClientQueryMessage(m) => m.fmt(f),
FsdMessageType::ClientQueryResponseMessage(m) => m.fmt(f),
FsdMessageType::HandoffOfferMessage(m) => m.fmt(f),
FsdMessageType::HandoffAcceptMessage(m) => m.fmt(f),
FsdMessageType::SharedStateMessage(m) => m.fmt(f),
}
}
}
#[allow(unused)]
#[derive(Clone, Debug)]
pub enum ClientQueryType {
IsValidATC {
atc_callsign: String,
}, Capabilities, Com1Freq, RealName, Server, ATIS, PublicIP, INF, FlightPlan {
aircraft_callsign: String,
}, ForceBeaconCode {
code: TransponderCode,
}, RequestRelief, CancelRequestRelief, HelpRequest {
message: Option<String>,
}, CancelHelpRequest {
message: Option<String>,
}, WhoHas {
aircraft_callsign: String,
}, InitiateTrack {
aircraft_callsign: String,
}, AcceptHandoff {
aircraft_callsign: String,
atc_callsign: String,
}, DropTrack {
aircraft_callsign: String,
}, SetFinalAltitude {
aircraft_callsign: String,
level: Level,
}, SetTempAltitude {
aircraft_callsign: String,
level: Level,
}, SetBeaconCode {
aircraft_callsign: String,
code: TransponderCode,
}, SetScratchpad {
aircraft_callsign: String,
contents: ScratchPad,
}, SetVoiceType {
aircraft_callsign: String,
voice_capability: VoiceCapability,
}, AircraftConfigurationRequest, AircraftConfigurationResponse {
aircraft_config: AircraftConfig,
}, SimTime {
time: DateTime<Utc>,
}, NewInfo {
atis_letter: char,
}, NewATIS {
atis_letter: char,
surface_wind: String,
pressure: String,
}, SetGlobalData {
aircraft_callsign: String,
contents: String,
}, ExitCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
}, EntryCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
}, TransferDirectlyTo {
aircraft_callsign: String,
receiving_atc_callsign: String
} }
impl Display for ClientQueryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientQueryType::IsValidATC { atc_callsign } => write!(f, "ATC:{}", atc_callsign),
ClientQueryType::Capabilities => write!(f, "CAPS"),
ClientQueryType::Com1Freq => write!(f, "C?"),
ClientQueryType::RealName => write!(f, "RN"),
ClientQueryType::Server => write!(f, "SV"),
ClientQueryType::ATIS => write!(f, "ATIS"),
ClientQueryType::PublicIP => write!(f, "IP"),
ClientQueryType::INF => write!(f, "INF"),
ClientQueryType::FlightPlan { aircraft_callsign } => {
write!(f, "FP:{}", aircraft_callsign)
}
ClientQueryType::RequestRelief => write!(f, "BY"),
ClientQueryType::CancelRequestRelief => write!(f, "HI"),
ClientQueryType::HelpRequest { message: None } => write!(f, "HLP"),
ClientQueryType::HelpRequest { message: Some(msg) } => write!(f, "HLP:{msg}"),
ClientQueryType::CancelHelpRequest { message: None } => write!(f, "NOHLP"),
ClientQueryType::CancelHelpRequest { message: Some(msg) } => write!(f, "NOHLP:{msg}"),
ClientQueryType::WhoHas { aircraft_callsign } => write!(f, "WH:{}", aircraft_callsign),
ClientQueryType::InitiateTrack { aircraft_callsign } => {
write!(f, "IT:{}", aircraft_callsign)
}
ClientQueryType::AcceptHandoff {
aircraft_callsign,
atc_callsign,
} => {
write!(f, "HT:{}:{}", aircraft_callsign, atc_callsign)
}
ClientQueryType::DropTrack { aircraft_callsign } => {
write!(f, "DR:{}", aircraft_callsign)
}
ClientQueryType::SetFinalAltitude {
aircraft_callsign,
level,
} => write!(f, "FA:{}:{}", aircraft_callsign, level),
ClientQueryType::SetTempAltitude {
aircraft_callsign,
level,
} => write!(f, "TA:{}:{}", aircraft_callsign, level),
ClientQueryType::SetBeaconCode {
aircraft_callsign,
code,
} => {
write!(f, "BC:{}:{}", aircraft_callsign, code)
}
ClientQueryType::ForceBeaconCode { code } => {
write!(f, "IPC:W:852:{}", code.as_bcd_format())
}
ClientQueryType::SetScratchpad {
aircraft_callsign,
contents,
} => {
write!(f, "SC:{}:{}", aircraft_callsign, contents)
}
ClientQueryType::SetVoiceType {
aircraft_callsign,
voice_capability,
} => {
write!(f, "VT:{}:{}", aircraft_callsign, voice_capability)
}
ClientQueryType::AircraftConfigurationRequest => {
write!(f, "ACC:{{\"request\":\"full\"}}")
}
ClientQueryType::AircraftConfigurationResponse { aircraft_config } => {
write!(f, "ACC:{}", aircraft_config)
}
ClientQueryType::NewATIS {
atis_letter,
surface_wind,
pressure,
} => {
write!(
f,
"NEWATIS:ATIS {}: {} - {}",
atis_letter, surface_wind, pressure
)
}
ClientQueryType::NewInfo { atis_letter } => {
write!(f, "NEWINFO:{}", atis_letter)
}
ClientQueryType::SimTime { time } => {
write!(f, "SIMTIME:{}", time.format("Y%m%d%H%M%S"))
}
ClientQueryType::SetGlobalData {
aircraft_callsign,
contents,
} => {
write!(f, "GD:{}:{}", aircraft_callsign, contents)
},
ClientQueryType::ExitCoordination { aircraft_callsign, point, level } => {
write!(f, "COPX:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientQueryType::EntryCoordination { aircraft_callsign, point, level } => {
write!(f, "COPN:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientQueryType::TransferDirectlyTo { aircraft_callsign, receiving_atc_callsign } => {
write!(f, "COCTR:{aircraft_callsign}:{receiving_atc_callsign}")
},
}
}
}
#[allow(unused)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AtisLine {
VoiceServer(String),
TextLine(String),
LogoffTime(Option<u16>),
EndMarker(usize),
AtisLetter(Option<char>),
}
impl Display for AtisLine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AtisLine::VoiceServer(voice_server) => write!(f, "V:{}", voice_server),
AtisLine::TextLine(text) => write!(f, "T:{}", text),
AtisLine::LogoffTime(Some(time)) => write!(f, "Z:{:04}z", time),
AtisLine::LogoffTime(None) => write!(f, "Z:z"),
AtisLine::EndMarker(num_lines) => write!(f, "E:{}", num_lines),
AtisLine::AtisLetter(Some(letter)) => write!(f, "A:{}", letter),
AtisLine::AtisLetter(None) => write!(f, "A:"),
}
}
}
#[allow(unused)]
#[derive(Clone, Debug)]
pub enum ClientResponseType {
Com1Freq {
frequency: RadioFrequency,
},
ATIS {
atis_line: AtisLine,
},
RealName {
name: String,
sector_file: String,
rating: u8,
},
Capabilities {
capabilities: Vec<ClientCapability>,
},
PublicIP {
ip_address: String,
},
Server {
hostname_or_ip_address: String,
},
IsValidATC {
atc_callsign: String,
valid_atc: bool,
},
AcceptExitCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
},
RefuseExitCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
},
AcceptEntryCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
},
RefuseEntryCoordination {
aircraft_callsign: String,
point: Option<String>,
level: Level
},
AcceptTransferDirectlyTo {
aircraft_callsign: String,
receiving_atc_callsign: String
},
RefuseTransferDirectlyTo {
aircraft_callsign: String,
receiving_atc_callsign: String
}
}
impl Display for ClientResponseType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClientResponseType::Com1Freq { frequency } => {
write!(f, "C?:{}", frequency.to_human_readable_string())
}
ClientResponseType::ATIS { atis_line } => write!(f, "ATIS:{}", atis_line),
ClientResponseType::RealName {
name,
sector_file,
rating,
} => {
write!(f, "RN:{}:{}:{}", name, sector_file, rating)
}
ClientResponseType::Capabilities { capabilities } => {
write!(f, "CAPS:")?;
let mut capabilities = capabilities.iter().peekable();
while let Some(capability) = capabilities.next() {
write!(f, "{}=1", capability)?;
if capabilities.peek().is_some() {
write!(f, ":")?;
}
}
Ok(())
}
ClientResponseType::PublicIP { ip_address } => write!(f, "IP:{}", ip_address),
ClientResponseType::Server {
hostname_or_ip_address,
} => write!(f, "SV:{}", hostname_or_ip_address),
ClientResponseType::IsValidATC {
atc_callsign,
valid_atc,
} => {
let valid = if *valid_atc { 'Y' } else { 'N' };
write!(f, "ATC:{}:{}", valid, atc_callsign)
},
ClientResponseType::AcceptExitCoordination { aircraft_callsign, point, level } => {
write!(f, "COPXOK:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientResponseType::RefuseExitCoordination { aircraft_callsign, point, level } => {
write!(f, "COPXNO:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientResponseType::AcceptEntryCoordination { aircraft_callsign, point, level } => {
write!(f, "COPNOK:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientResponseType::RefuseEntryCoordination { aircraft_callsign, point, level } => {
write!(f, "COPNNO:{aircraft_callsign}:{}:{level}", point.as_deref().unwrap_or_default())
},
ClientResponseType::AcceptTransferDirectlyTo { aircraft_callsign, receiving_atc_callsign } => {
write!(f, "COCTROK:{aircraft_callsign}:{receiving_atc_callsign}")
},
ClientResponseType::RefuseTransferDirectlyTo { aircraft_callsign, receiving_atc_callsign } => {
write!(f, "COCTRNO:{aircraft_callsign}:{receiving_atc_callsign}")
},
}
}
}
#[allow(unused)]
#[derive(Clone, Debug)]
pub enum SharedStateType {
Version,
ID,
DI,
IHave {
aircraft_callsign: String,
},
ScratchPad {
aircraft_callsign: String,
contents: ScratchPad,
},
TempAltitude {
aircraft_callsign: String,
level: Level,
},
FinalAltitude {
aircraft_callsign: String,
level: Level,
},
VoiceType {
aircraft_callsign: String,
voice_capability: VoiceCapability,
},
BeaconCode {
aircraft_callsign: String,
code: TransponderCode,
},
HandoffCancel {
aircraft_callsign: String,
},
FlightStrip {
aircraft_callsign: String,
format: Option<i32>,
contents: Option<Vec<String>>,
},
PushToDepartureList {
aircraft_callsign: String,
},
PointOut {
aircraft_callsign: String,
},
LandLine {
landline_type: LandLineType,
landline_command: LandLineCommand,
},
GlobalData {
aircraft_callsign: String,
contents: String,
},
}
impl Display for SharedStateType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SharedStateType::Version => write!(f, "VER"),
SharedStateType::ID => write!(f, "ID"),
SharedStateType::DI => write!(f, "DI"),
SharedStateType::IHave { aircraft_callsign } => write!(f, "IH:{}", aircraft_callsign),
SharedStateType::ScratchPad {
aircraft_callsign,
contents,
} => {
write!(f, "SC:{}:{}", aircraft_callsign, contents)
}
SharedStateType::TempAltitude {
aircraft_callsign,
level,
} => {
write!(f, "TA:{}:{}", aircraft_callsign, level)
}
SharedStateType::FinalAltitude {
aircraft_callsign,
level,
} => {
write!(f, "FA:{}:{}", aircraft_callsign, level)
}
SharedStateType::VoiceType {
aircraft_callsign,
voice_capability,
} => {
write!(f, "VT:{}:{}", aircraft_callsign, voice_capability)
}
SharedStateType::BeaconCode {
aircraft_callsign,
code,
} => write!(f, "BC:{}:{}", aircraft_callsign, code),
SharedStateType::HandoffCancel { aircraft_callsign } => {
write!(f, "HC:{}", aircraft_callsign)
}
SharedStateType::PointOut { aircraft_callsign } => {
write!(f, "PT:{}", aircraft_callsign)
}
SharedStateType::PushToDepartureList { aircraft_callsign } => {
write!(f, "DP:{}", aircraft_callsign)
}
SharedStateType::FlightStrip {
aircraft_callsign,
format,
contents,
} => {
write!(f, "ST:{aircraft_callsign}")?;
if let Some(format) = *format {
write!(f, ":{format}")?;
}
if let Some(contents) = contents {
for item in contents {
write!(f, ":{item}")?;
}
}
Ok(())
}
SharedStateType::LandLine {
landline_type,
landline_command,
} => match (*landline_type, *landline_command) {
(LandLineType::Intercom, LandLineCommand::Request { ip_address, port }) => {
write!(f, "IC:{ip_address}:{port}")
}
(LandLineType::Intercom, LandLineCommand::Approve { ip_address, port }) => {
write!(f, "IK:{ip_address}:{port}")
}
(LandLineType::Intercom, LandLineCommand::Reject) => write!(f, "IB"),
(LandLineType::Intercom, LandLineCommand::End) => write!(f, "EC"),
(LandLineType::Override, LandLineCommand::Request { ip_address, port }) => {
write!(f, "OV:{ip_address}:{port}")
}
(LandLineType::Override, LandLineCommand::Approve { ip_address, port }) => {
write!(f, "OK:{ip_address}:{port}")
}
(LandLineType::Override, LandLineCommand::Reject) => write!(f, "OB"),
(LandLineType::Override, LandLineCommand::End) => write!(f, "EO"),
(LandLineType::Monitor, LandLineCommand::Request { ip_address, port }) => {
write!(f, "MN:{ip_address}:{port}")
}
(LandLineType::Monitor, LandLineCommand::Approve { ip_address, port }) => {
write!(f, "MK:{ip_address}:{port}")
}
(LandLineType::Monitor, LandLineCommand::Reject) => write!(f, "MB"),
(LandLineType::Monitor, LandLineCommand::End) => write!(f, "EM"),
},
SharedStateType::GlobalData {
aircraft_callsign,
contents,
} => write!(f, "GD:{aircraft_callsign}:{contents}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LandLineType {
Intercom,
Override,
Monitor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LandLineCommand {
Request { ip_address: Ipv4Addr, port: u16 },
Approve { ip_address: Ipv4Addr, port: u16 },
Reject,
End,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operator {
Exactly,
OrLess,
OrGreater,
}
impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Exactly => write!(f, "="),
Self::OrLess => write!(f, "-"),
Self::OrGreater => write!(f, "+"),
}
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub enum GroundState {
#[default]
NoState,
OnFrequency,
DeIcing,
Startup,
Pushback,
Taxi,
LineUp,
TakeOff,
Arriving,
TaxiIn,
OnBlock,
}
impl Display for GroundState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoState => write!(f, "NSTS"),
Self::OnFrequency => write!(f, "ONFREQ"),
Self::DeIcing => write!(f, "DE-ICE"),
Self::Startup => write!(f, "STUP"),
Self::Pushback => write!(f, "PUSH"),
Self::Taxi => write!(f, "TAXI"),
Self::LineUp => write!(f, "LINEUP"),
Self::TakeOff => write!(f, "DEPA"),
Self::Arriving => write!(f, "ARR"),
Self::TaxiIn => write!(f, "TXIN"),
Self::OnBlock => write!(f, "PARK"),
}
}
}
#[derive(Debug, Clone)]
pub enum ScratchPad {
PlainTextOrDirect(String),
RateOfClimbDescent(u32),
Heading(u32),
Speed(u32),
Mach(u32),
SpeedOperator(Operator),
RateOfClimbDescentOperator(Operator),
Stand(String),
CancelledStand,
ManualStand(String, String),
CancelledManualStand,
ClearanceReceived,
ClearanceCancelled,
GroundState(GroundState),
MissedApproach,
}
impl FromStr for ScratchPad {
type Err = FsdMessageParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(Ok(h)) = s.strip_prefix('H').map(str::parse) {
return Ok(Self::Heading(h));
}
if let Some(Ok(r)) = s.strip_prefix('R').map(str::parse) {
return Ok(Self::RateOfClimbDescent(r));
}
if let Some(Ok(speed)) = s.strip_prefix('S').map(str::parse) {
return Ok(Self::Speed(speed));
}
if let Some(Ok(m)) = s.strip_prefix('M').map(str::parse) {
return Ok(Self::Mach(m));
}
if s.len() > 6 && s.starts_with("GRP/S/") {
return Ok(Self::Stand(s[6..].to_string()));
}
if s.len() > 6 && s.starts_with("GRP/M/") {
let mut split = s.split('/');
return Ok(Self::ManualStand(
split.next().unwrap_or("ZZZZ").to_string(),
split.next().unwrap_or("").to_string(),
));
}
match s {
"/ASP=/" => Ok(Self::SpeedOperator(Operator::Exactly)),
"/ASP-/" => Ok(Self::SpeedOperator(Operator::OrLess)),
"/ASP+/" => Ok(Self::SpeedOperator(Operator::OrGreater)),
"/ARC=/" => Ok(Self::RateOfClimbDescentOperator(Operator::Exactly)),
"/ARC-/" => Ok(Self::RateOfClimbDescentOperator(Operator::OrLess)),
"/ARC+/" => Ok(Self::RateOfClimbDescentOperator(Operator::OrGreater)),
"GRP/S/" => Ok(Self::CancelledStand),
"GRP/M/" => Ok(Self::CancelledManualStand),
"NSTS" => Ok(Self::GroundState(GroundState::NoState)),
"NOSTATE" => Ok(Self::GroundState(GroundState::NoState)),
"ONFREQ" => Ok(Self::GroundState(GroundState::OnFrequency)),
"DE-ICE" => Ok(Self::GroundState(GroundState::DeIcing)),
"STUP" | "ST-UP" => Ok(Self::GroundState(GroundState::Startup)),
"PUSH" => Ok(Self::GroundState(GroundState::Pushback)),
"TAXI" => Ok(Self::GroundState(GroundState::Taxi)),
"LINEUP" => Ok(Self::GroundState(GroundState::LineUp)),
"TXIN" => Ok(Self::GroundState(GroundState::TaxiIn)),
"DEPA" => Ok(Self::GroundState(GroundState::TakeOff)),
"ARR" => Ok(Self::GroundState(GroundState::Arriving)),
"PARK" => Ok(Self::GroundState(GroundState::OnBlock)),
"CLEA" => Ok(Self::ClearanceReceived),
"NOTC" => Ok(Self::ClearanceCancelled),
"MISAP_" => Ok(Self::MissedApproach),
text => Ok(ScratchPad::PlainTextOrDirect(text.to_string())),
}
}
}
impl Display for ScratchPad {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RateOfClimbDescent(r) => write!(f, "R{r}"),
Self::Heading(h) => write!(f, "H{h}"),
Self::Speed(speed) => write!(f, "S{speed}"),
Self::Mach(m) => write!(f, "{m}"),
Self::SpeedOperator(op) => write!(f, "/ASP{op}/"),
Self::RateOfClimbDescentOperator(op) => write!(f, "/ARC{op}/"),
Self::PlainTextOrDirect(text) => write!(f, "{text}"),
Self::Stand(stand) => write!(f, "GRP/S/{stand}"),
Self::CancelledStand => write!(f, "GRP/S/"),
Self::ManualStand(icao, stand) => write!(f, "GRP/M/{icao}/{stand}"),
Self::CancelledManualStand => write!(f, "GRP/M"),
Self::GroundState(gs) => gs.fmt(f),
Self::ClearanceReceived => write!(f, "CLEA"),
Self::ClearanceCancelled => write!(f, "NOTC"),
Self::MissedApproach => write!(f, "MISAP_"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VoiceCapability {
#[default]
Voice,
Text,
Receive,
}
impl<S: AsRef<str>> From<S> for VoiceCapability {
fn from(value: S) -> Self {
let value = value.as_ref();
match value.to_lowercase().as_str() {
"t" => VoiceCapability::Text,
"r" => VoiceCapability::Receive,
_ => VoiceCapability::default()
}
}
}
impl Display for VoiceCapability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
VoiceCapability::Voice => write!(f, "v"),
VoiceCapability::Text => write!(f, "t"),
VoiceCapability::Receive => write!(f, "r"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
VFR,
FlightLevel(i32),
Altitude(i32),
}
impl FromStr for Level {
type Err = FsdMessageParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.is_empty() {
Ok(Self::FlightLevel(0))
} else if input == "VFR" {
Ok(Self::VFR)
} else {
let flight_level = input.to_uppercase().starts_with("FL");
let input_trimmed = if flight_level { &input[2..] } else { input };
let as_num: i32 = input_trimmed
.parse()
.map_err(|_| FsdMessageParseError::InvalidLevel(input.to_string()))?;
Ok(if flight_level {
Self::FlightLevel(as_num * 100)
} else {
Self::FlightLevel(as_num)
})
}
}
}
impl Display for Level {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Level::VFR => f.write_str("VFR"),
Level::FlightLevel(fl) => f.write_fmt(format_args!("{fl:.0}")),
Level::Altitude(a) => f.write_fmt(format_args!("{a:.0}")),
}
}
}