fsd_interface/
enums.rs

1use std::collections::HashSet;
2use std::net::Ipv4Addr;
3use std::{fmt::Display, str::FromStr};
4
5use crate::messages::*;
6use crate::structs::{RadioFrequency, TransponderCode};
7use crate::{aircraft_config::AircraftConfig, errors::FsdMessageParseError};
8use chrono::{DateTime, Utc};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum ClientCapability {
12    Version,
13    ATCInfo,
14    ModelDesc,
15    ACConfig,
16    VisUpdate,
17    RadarUpdate,
18    ATCMulti,
19    SecPos,
20    IcaoEq,
21    FastPos,
22    OngoingCoord,
23    InterimPos,
24    Stealth,
25    Teamspeak,
26    NewATIS,
27    Mumble,
28    GlobalData,
29    Simulated,
30    ObsPilot,
31}
32impl FromStr for ClientCapability {
33    type Err = FsdMessageParseError;
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        match s.to_uppercase().as_str() {
36            "VERSION" => Ok(ClientCapability::Version),
37            "ATCINFO" => Ok(ClientCapability::ATCInfo),
38            "MODELDESC" => Ok(ClientCapability::ModelDesc),
39            "ACCONFIG" => Ok(ClientCapability::ACConfig),
40            "VISUPDATE" => Ok(ClientCapability::VisUpdate),
41            "RADARUPDATE" => Ok(ClientCapability::RadarUpdate),
42            "ATCMULTI" => Ok(ClientCapability::ATCMulti),
43            "SECPOS" => Ok(ClientCapability::SecPos),
44            "ICAOEQ" => Ok(ClientCapability::IcaoEq),
45            "FASTPOS" => Ok(ClientCapability::FastPos),
46            "ONGOINGCOORD" => Ok(ClientCapability::OngoingCoord),
47            "INTERIMPOS" => Ok(ClientCapability::InterimPos),
48            "STEALTH" => Ok(ClientCapability::Stealth),
49            "TEAMSPEAK" => Ok(ClientCapability::Teamspeak),
50            "NEWATIS" => Ok(ClientCapability::NewATIS),
51            "MUMBLE" => Ok(ClientCapability::Mumble),
52            "GLOBALDATA" => Ok(ClientCapability::GlobalData),
53            "SIMULATED" => Ok(ClientCapability::Simulated),
54            "OBSPILOT" => Ok(ClientCapability::ObsPilot),
55            _ => Err(FsdMessageParseError::InvalidClientCapability(s.to_string())),
56        }
57    }
58}
59impl Display for ClientCapability {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match *self {
62            ClientCapability::ACConfig => write!(f, "ACCONFIG"),
63            ClientCapability::ATCInfo => write!(f, "ATCINFO"),
64            ClientCapability::ModelDesc => write!(f, "MODELDESC"),
65            ClientCapability::Version => write!(f, "VERSION"),
66            ClientCapability::VisUpdate => write!(f, "VISUPDATE"),
67            ClientCapability::RadarUpdate => write!(f, "RADARUPDATE"),
68            ClientCapability::ATCMulti => write!(f, "ATCMULTI"),
69            ClientCapability::SecPos => write!(f, "SECPOS"),
70            ClientCapability::IcaoEq => write!(f, "ICAOEQ"),
71            ClientCapability::FastPos => write!(f, "FASTPOS"),
72            ClientCapability::OngoingCoord => write!(f, "ONGOINGCOORD"),
73            ClientCapability::InterimPos => write!(f, "INTERIMPOS"),
74            ClientCapability::Stealth => write!(f, "STEALTH"),
75            ClientCapability::Teamspeak => write!(f, "TEAMSPEAK"),
76            ClientCapability::NewATIS => write!(f, "NEWATIS"),
77            ClientCapability::Mumble => write!(f, "MUMBLE"),
78            ClientCapability::GlobalData => write!(f, "GLOBALDATA"),
79            ClientCapability::Simulated => write!(f, "SIMULATED"),
80            ClientCapability::ObsPilot => write!(f, "OBSPILOT"),
81        }
82    }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum AtcRating {
87    Observer = 1,
88    S1,
89    S2,
90    S3,
91    C1,
92    C2,
93    C3,
94    I1,
95    I2,
96    I3,
97    Supervisor,
98    Administrator,
99}
100impl FromStr for AtcRating {
101    type Err = FsdMessageParseError;
102    fn from_str(s: &str) -> Result<Self, Self::Err> {
103        let rating_u8 =
104            u8::from_str(s).map_err(|_| FsdMessageParseError::InvalidRating(s.to_string()))?;
105        match rating_u8 {
106            1 => Ok(AtcRating::Observer),
107            2 => Ok(AtcRating::S1),
108            3 => Ok(AtcRating::S2),
109            4 => Ok(AtcRating::S3),
110            5 => Ok(AtcRating::C1),
111            6 => Ok(AtcRating::C2),
112            7 => Ok(AtcRating::C3),
113            8 => Ok(AtcRating::I1),
114            9 => Ok(AtcRating::I2),
115            10 => Ok(AtcRating::I3),
116            11 => Ok(AtcRating::Supervisor),
117            12 => Ok(AtcRating::Administrator),
118            _ => Err(FsdMessageParseError::InvalidRating(s.to_string())),
119        }
120    }
121}
122
123#[derive(Debug, Clone, Copy)]
124pub enum PilotRating {
125    Student = 1,
126    VFR,
127    IFR,
128    Instructor,
129    Supervisor,
130}
131
132impl FromStr for PilotRating {
133    type Err = FsdMessageParseError;
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        let rating_u8 =
136            u8::from_str(s).map_err(|_| FsdMessageParseError::InvalidRating(s.to_string()))?;
137        match rating_u8 {
138            1 => Ok(PilotRating::Student),
139            2 => Ok(PilotRating::VFR),
140            3 => Ok(PilotRating::IFR),
141            4 => Ok(PilotRating::Instructor),
142            5 => Ok(PilotRating::Supervisor),
143            _ => Err(FsdMessageParseError::InvalidRating(s.to_string())),
144        }
145    }
146}
147
148/// Represents a version of the FSD protocol
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum ProtocolRevision {
151    /// Used on legacy FSD servers. If the FSD server is a privately run one, it is most likely using this version
152    Classic = 9,
153    /// Deprecated - used on VATSIM prior to the introduction of client authentication
154    VatsimNoAuth = 10,
155    /// Used on VATSIM servers until 2022
156    VatsimAuth = 100,
157    /// VATSIM Velocity - used on VATSIM servers since 2022
158    Vatsim2022 = 101,
159}
160impl FromStr for ProtocolRevision {
161    type Err = FsdMessageParseError;
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        match s {
164            "1" | "9" => Ok(Self::Classic),
165            "10" => Ok(Self::VatsimNoAuth),
166            "100" => Ok(Self::VatsimAuth),
167            "101" => Ok(Self::Vatsim2022),
168            _ => Err(FsdMessageParseError::InvalidProtocolRevison(s.to_string())),
169        }
170    }
171}
172
173#[derive(Debug, Clone, Copy)]
174pub enum SimulatorType {
175    Unknown,
176    MSFS95,
177    MSFS98,
178    MSCFS,
179    MSFS2000,
180    MSCFS2,
181    MSFS2002,
182    MSCFS3,
183    MSFS2004,
184    MSFSX,
185    MSFS,
186    MSFS2024,
187    XPLANE8,
188    XPLANE9,
189    XPLANE10,
190    XPLANE11,
191    XPLANE12,
192    P3Dv1,
193    P3Dv2,
194    P3Dv3,
195    P3Dv4,
196    P3Dv5,
197    FlightGear,
198}
199impl<S: AsRef<str>> From<S> for SimulatorType {
200    fn from(value: S) -> Self {
201        match value.as_ref() {
202            "1" => SimulatorType::MSFS95,
203            "2" => SimulatorType::MSFS98,
204            "3" => SimulatorType::MSCFS,
205            "4" => SimulatorType::MSFS2000,
206            "5" => SimulatorType::MSCFS2,
207            "6" => SimulatorType::MSFS2002,
208            "7" => SimulatorType::MSCFS3,
209            "8" => SimulatorType::MSFS2004,
210            "9" => SimulatorType::MSFSX,
211            "10" => SimulatorType::MSFS,
212            "11" => SimulatorType::MSFS2024,
213            "12" => SimulatorType::XPLANE8,
214            "13" => SimulatorType::XPLANE9,
215            "14" => SimulatorType::XPLANE10,
216            "15" => SimulatorType::XPLANE11,
217            "16" => SimulatorType::XPLANE12,
218            "17" => SimulatorType::P3Dv1,
219            "18" => SimulatorType::P3Dv2,
220            "19" => SimulatorType::P3Dv3,
221            "20" => SimulatorType::P3Dv4,
222            "21" => SimulatorType::P3Dv5,
223            "22" => SimulatorType::FlightGear,
224            _ => SimulatorType::Unknown,
225        }
226    }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum FlightRules {
231    DVFR,
232    SVFR,
233    VFR,
234    IFR,
235}
236
237impl FromStr for FlightRules {
238    type Err = FsdMessageParseError;
239    fn from_str(s: &str) -> Result<Self, Self::Err> {
240        let s = s.to_uppercase();
241        match s.as_str() {
242            "D" => Ok(FlightRules::DVFR),
243            "S" => Ok(FlightRules::SVFR),
244            "V" => Ok(FlightRules::VFR),
245            "I" => Ok(FlightRules::IFR),
246            _ => Err(FsdMessageParseError::InvalidFlightRules(s)),
247        }
248    }
249}
250
251impl Display for FlightRules {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        match *self {
254            FlightRules::DVFR => write!(f, "D"),
255            FlightRules::VFR => write!(f, "V"),
256            FlightRules::SVFR => write!(f, "S"),
257            FlightRules::IFR => write!(f, "I"),
258        }
259    }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum AtcType {
264    Observer,
265    FlightServiceStation,
266    Delivery,
267    Ground,
268    Tower,
269    Approach,
270    Centre,
271}
272
273impl FromStr for AtcType {
274    type Err = FsdMessageParseError;
275    fn from_str(s: &str) -> Result<Self, Self::Err> {
276        match s {
277            "0" => Ok(AtcType::Observer),
278            "1" => Ok(AtcType::FlightServiceStation),
279            "2" => Ok(AtcType::Delivery),
280            "3" => Ok(AtcType::Ground),
281            "4" => Ok(AtcType::Tower),
282            "5" => Ok(AtcType::Approach),
283            "6" => Ok(AtcType::Centre),
284            _ => Err(FsdMessageParseError::InvalidAtcType(s.to_string())),
285        }
286    }
287}
288
289#[derive(Debug, Clone, Copy)]
290pub enum TransponderMode {
291    Standby,
292    ModeC,
293    Ident,
294}
295impl FromStr for TransponderMode {
296    type Err = FsdMessageParseError;
297    fn from_str(s: &str) -> Result<Self, Self::Err> {
298        match s {
299            "S" => Ok(TransponderMode::Standby),
300            "N" => Ok(TransponderMode::ModeC),
301            "Y" => Ok(TransponderMode::Ident),
302            _ => Err(FsdMessageParseError::InvalidTransponderMode(s.to_string())),
303        }
304    }
305}
306impl Display for TransponderMode {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        match *self {
309            Self::Standby => write!(f, "S"),
310            Self::ModeC => write!(f, "N"),
311            Self::Ident => write!(f, "Y"),
312        }
313    }
314}
315
316#[derive(Clone, Debug)]
317pub enum FsdMessageType {
318    AtcRegisterMessage(AtcRegisterMessage),
319    PilotRegisterMessage(PilotRegisterMessage),
320    AtcDeregisterMessage(AtcDeregisterMessage),
321    PilotDeregisterMessage(PilotDeregisterMessage),
322    AtcPositionUpdateMessage(AtcPositionUpdateMessage),
323    AtcSecondaryVisCentreMessage(AtcSecondaryVisCentreMessage),
324    PilotPositionUpdateMessage(PilotPositionUpdateMessage),
325    AuthenticationChallengeMessage(AuthenticationChallengeMessage),
326    AuthenticationResponseMessage(AuthenticationResponseMessage),
327    TextMessage(TextMessage),
328    FrequencyMessage(FrequencyMessage),
329    ChangeServerMessage(ChangeServerMessage),
330    InitialServerHandshakeMessage(InitialServerHandshakeMessage),
331    InitialClientHandshakeMessage(InitialClientHandshakeMessage),
332    SendFastPositionUpdatesMessage(SendFastPositionUpdatesMessage),
333    VelocityPositionStoppedMessage(VelocityPositionStoppedMessage),
334    VelocityPositionSlowMessage(VelocityPositionSlowMessage),
335    VelocityPositionFastMessage(VelocityPositionFastMessage),
336    KillMessage(KillMessage),
337    MetarRequestMessage(MetarRequestMessage),
338    MetarResponseMessage(MetarResponseMessage),
339    PingMessage(PingMessage),
340    PongMessage(PongMessage),
341    PlaneInfoRequestMessage(PlaneInfoRequestMessage),
342    PlaneInfoResponseMessage(PlaneInfoResponseMessage),
343    FsdErrorMessage(FsdErrorMessage),
344    FlightPlanMessage(FlightPlanMessage),
345    FlightPlanAmendmentMessage(FlightPlanAmendmentMessage),
346    FSInnPlaneInformationRequestMessage,
347    FSInnPlaneInformationResponseMessage,
348    ServerHeartbeat,
349    ClientQueryMessage(ClientQueryMessage),
350    ClientQueryResponseMessage(ClientQueryResponseMessage),
351    HandoffOfferMessage(HandoffOfferMessage),
352    HandoffAcceptMessage(HandoffAcceptMessage),
353    SharedStateMessage(SharedStateMessage),
354}
355
356impl FsdMessageType {
357    pub(crate) fn identify(message: &str) -> Result<FsdMessageType, FsdMessageParseError> {
358        let fields: Vec<&str> = message.split(':').collect();
359        if fields.len() < 2 {
360            if fields[0].starts_with("#DA") {
361                return Ok(Self::AtcDeregisterMessage(fields.as_slice().try_into()?));
362            }
363            if fields[0].starts_with("#DP") {
364                return Ok(Self::PilotDeregisterMessage(fields.as_slice().try_into()?));
365            }
366            return Err(FsdMessageParseError::UnknownMessageType(
367                message.to_string(),
368            ));
369        }
370        if fields[0].starts_with("#DA") {
371            return Ok(Self::AtcDeregisterMessage(fields.as_slice().try_into()?));
372        }
373        if fields[0].starts_with("#DP") {
374            return Ok(Self::PilotDeregisterMessage(fields.as_slice().try_into()?));
375        }
376        if fields[0].starts_with("#AA") {
377            return Ok(Self::AtcRegisterMessage(fields.as_slice().try_into()?));
378        }
379        if fields[0].starts_with("#AP") {
380            return Ok(Self::PilotRegisterMessage(fields.as_slice().try_into()?));
381        }
382
383        if fields[0].starts_with('%') {
384            return Ok(Self::AtcPositionUpdateMessage(
385                fields.as_slice().try_into()?,
386            ));
387        }
388        if fields[0].starts_with('\'') {
389            return Ok(Self::AtcSecondaryVisCentreMessage(
390                fields.as_slice().try_into()?,
391            ));
392        }
393        if fields[0].starts_with('@') {
394            return Ok(Self::PilotPositionUpdateMessage(
395                fields.as_slice().try_into()?,
396            ));
397        }
398        if fields[0].starts_with("$ZC") {
399            return Ok(Self::AuthenticationChallengeMessage(
400                fields.as_slice().try_into()?,
401            ));
402        }
403        if fields[0].starts_with("$ZR") {
404            return Ok(Self::AuthenticationResponseMessage(
405                fields.as_slice().try_into()?,
406            ));
407        }
408        if fields[0].starts_with("$ER") {
409            return Ok(Self::FsdErrorMessage(fields.as_slice().try_into()?));
410        }
411        if fields[0].starts_with("$HO") {
412            return Ok(Self::HandoffOfferMessage(fields.as_slice().try_into()?));
413        }
414        if fields[0].starts_with("$HA") {
415            return Ok(Self::HandoffAcceptMessage(fields.as_slice().try_into()?));
416        }
417        if fields[0].starts_with("#TM") {
418            if fields[1].starts_with('@') {
419                return Ok(Self::FrequencyMessage(fields.as_slice().try_into()?));
420            }
421
422            return Ok(Self::TextMessage(fields.as_slice().try_into()?));
423        }
424        if fields[0].starts_with("$XX") {
425            return Ok(Self::ChangeServerMessage(fields.as_slice().try_into()?));
426        }
427        if fields[0].starts_with("$FP") {
428            return Ok(Self::FlightPlanMessage(fields.as_slice().try_into()?));
429        }
430        if fields[0].starts_with("$AM") {
431            return Ok(Self::FlightPlanAmendmentMessage(
432                fields.as_slice().try_into()?,
433            ));
434        }
435        if fields[0].starts_with("$DI") {
436            return Ok(Self::InitialServerHandshakeMessage(
437                fields.as_slice().try_into()?,
438            ));
439        }
440        if fields[0].starts_with("$ID") {
441            return Ok(Self::InitialClientHandshakeMessage(
442                fields.as_slice().try_into()?,
443            ));
444        }
445        if fields[0].starts_with("$SF") {
446            return Ok(Self::SendFastPositionUpdatesMessage(
447                fields.as_slice().try_into()?,
448            ));
449        }
450        if fields[0].starts_with("#ST") {
451            return Ok(Self::VelocityPositionStoppedMessage(
452                fields.as_slice().try_into()?,
453            ));
454        }
455        if fields[0].starts_with("#DL") {
456            return Ok(Self::ServerHeartbeat);
457        }
458        if fields[0].starts_with("#SL") {
459            return Ok(Self::VelocityPositionSlowMessage(
460                fields.as_slice().try_into()?,
461            ));
462        }
463        if fields[0].starts_with("#PC") {
464            return Ok(Self::SharedStateMessage(fields.as_slice().try_into()?));
465        }
466        if fields[0].starts_with('^') {
467            return Ok(Self::VelocityPositionFastMessage(
468                fields.as_slice().try_into()?,
469            ));
470        }
471        if fields[0].starts_with("$!!") {
472            return Ok(Self::KillMessage(fields.as_slice().try_into()?));
473        }
474        if fields[0].starts_with("$AX") {
475            return Ok(Self::MetarRequestMessage(fields.as_slice().try_into()?));
476        }
477        if fields[0].starts_with("$AR") {
478            return Ok(Self::MetarResponseMessage(fields.as_slice().try_into()?));
479        }
480        if fields[0].starts_with("$CQ") {
481            return Ok(Self::ClientQueryMessage(fields.as_slice().try_into()?));
482        }
483        if fields[0].starts_with("$CR") {
484            return Ok(Self::ClientQueryResponseMessage(
485                fields.as_slice().try_into()?,
486            ));
487        }
488        if fields[0].starts_with("$PI") {
489            return Ok(Self::PingMessage(fields.as_slice().try_into()?));
490        }
491        if fields[0].starts_with("$PO") {
492            return Ok(Self::PongMessage(fields.as_slice().try_into()?));
493        }
494        if fields[0].starts_with("#SB") {
495            if fields[2] == "PIR" {
496                return Ok(Self::PlaneInfoRequestMessage(fields.as_slice().try_into()?));
497            }
498            if fields[2] == "PI" {
499                return Ok(Self::PlaneInfoResponseMessage(
500                    fields.as_slice().try_into()?,
501                ));
502            }
503            if fields[2] == "FSIPI" {
504                return Ok(Self::FSInnPlaneInformationResponseMessage);
505            }
506            if fields[2] == "FSIPIR" {
507                return Ok(Self::FSInnPlaneInformationRequestMessage);
508            }
509        }
510
511        Err(FsdMessageParseError::UnknownMessageType(
512            message.to_string(),
513        ))
514    }
515}
516impl Display for FsdMessageType {
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        match self {
519            FsdMessageType::AtcRegisterMessage(m) => m.fmt(f),
520            FsdMessageType::PilotRegisterMessage(m) => m.fmt(f),
521            FsdMessageType::AtcDeregisterMessage(m) => m.fmt(f),
522            FsdMessageType::PilotDeregisterMessage(m) => m.fmt(f),
523            FsdMessageType::AtcPositionUpdateMessage(m) => m.fmt(f),
524            FsdMessageType::AtcSecondaryVisCentreMessage(m) => m.fmt(f),
525            FsdMessageType::PilotPositionUpdateMessage(m) => m.fmt(f),
526            FsdMessageType::AuthenticationChallengeMessage(m) => m.fmt(f),
527            FsdMessageType::AuthenticationResponseMessage(m) => m.fmt(f),
528            FsdMessageType::TextMessage(m) => m.fmt(f),
529            FsdMessageType::FrequencyMessage(m) => m.fmt(f),
530            FsdMessageType::ChangeServerMessage(m) => m.fmt(f),
531            FsdMessageType::InitialServerHandshakeMessage(m) => m.fmt(f),
532            FsdMessageType::InitialClientHandshakeMessage(m) => m.fmt(f),
533            FsdMessageType::SendFastPositionUpdatesMessage(m) => m.fmt(f),
534            FsdMessageType::VelocityPositionStoppedMessage(m) => m.fmt(f),
535            FsdMessageType::VelocityPositionSlowMessage(m) => m.fmt(f),
536            FsdMessageType::VelocityPositionFastMessage(m) => m.fmt(f),
537            FsdMessageType::KillMessage(m) => m.fmt(f),
538            FsdMessageType::MetarRequestMessage(m) => m.fmt(f),
539            FsdMessageType::MetarResponseMessage(m) => m.fmt(f),
540            FsdMessageType::PingMessage(m) => m.fmt(f),
541            FsdMessageType::PongMessage(m) => m.fmt(f),
542            FsdMessageType::PlaneInfoRequestMessage(m) => m.fmt(f),
543            FsdMessageType::PlaneInfoResponseMessage(m) => m.fmt(f),
544            FsdMessageType::FsdErrorMessage(m) => m.fmt(f),
545            FsdMessageType::FlightPlanMessage(m) => m.fmt(f),
546            FsdMessageType::FlightPlanAmendmentMessage(m) => m.fmt(f),
547            m @ FsdMessageType::FSInnPlaneInformationRequestMessage => m.fmt(f),
548            m @ FsdMessageType::FSInnPlaneInformationResponseMessage => m.fmt(f),
549            m @ FsdMessageType::ServerHeartbeat => m.fmt(f),
550            FsdMessageType::ClientQueryMessage(m) => m.fmt(f),
551            FsdMessageType::ClientQueryResponseMessage(m) => m.fmt(f),
552            FsdMessageType::HandoffOfferMessage(m) => m.fmt(f),
553            FsdMessageType::HandoffAcceptMessage(m) => m.fmt(f),
554            FsdMessageType::SharedStateMessage(m) => m.fmt(f),
555        }
556    }
557}
558
559#[allow(unused)]
560#[derive(Clone, Debug)]
561pub enum ClientQueryType {
562    IsValidATC {
563        atc_callsign: String,
564    }, //ATC
565    Capabilities, //CAPS
566    Com1Freq,     //C?
567    RealName,     //RN
568    Server,       //SV
569    ATIS,         //ATIS
570    PublicIP,     //IP
571    INF,          //INF
572    FlightPlan {
573        aircraft_callsign: String,
574    }, //FP
575    ForceBeaconCode {
576        code: TransponderCode,
577    }, //IPC:W:852
578    RequestRelief, //BY
579    CancelRequestRelief, //HI
580    HelpRequest {
581        message: Option<String>,
582    }, //HLP
583    CancelHelpRequest {
584        message: Option<String>,
585    }, //NOHLP
586    WhoHas {
587        aircraft_callsign: String,
588    }, //WH
589    InitiateTrack {
590        aircraft_callsign: String,
591    }, //IT
592    AcceptHandoff {
593        aircraft_callsign: String,
594        atc_callsign: String,
595    }, //HT
596    DropTrack {
597        aircraft_callsign: String,
598    }, //DR
599    SetFinalAltitude {
600        aircraft_callsign: String,
601        altitude: i32,
602    }, //FA
603    SetTempAltitude {
604        aircraft_callsign: String,
605        altitude: i32,
606    }, //TA
607    SetBeaconCode {
608        aircraft_callsign: String,
609        code: TransponderCode,
610    }, //BC
611    SetScratchpad {
612        aircraft_callsign: String,
613        contents: ScratchPad,
614    }, //SC
615    SetVoiceType {
616        aircraft_callsign: String,
617        voice_capability: VoiceCapability,
618    }, //VT
619    AircraftConfigurationRequest, //ACC
620    AircraftConfigurationResponse {
621        aircraft_config: AircraftConfig,
622    }, //ACC
623    SimTime {
624        time: DateTime<Utc>,
625    }, //SIMTIME
626    NewInfo {
627        atis_letter: char,
628    }, //NEWINFO
629    NewATIS {
630        atis_letter: char,
631        surface_wind: String,
632        pressure: String,
633    }, //NEWATIS
634    //Estimate,                                                                     //EST
635    SetGlobalData {
636        aircraft_callsign: String,
637        contents: String,
638    }, //GD
639}
640
641impl Display for ClientQueryType {
642    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643        match self {
644            ClientQueryType::IsValidATC { atc_callsign } => write!(f, "ATC:{}", atc_callsign),
645            ClientQueryType::Capabilities => write!(f, "CAPS"),
646            ClientQueryType::Com1Freq => write!(f, "C?"),
647            ClientQueryType::RealName => write!(f, "RN"),
648            ClientQueryType::Server => write!(f, "SV"),
649            ClientQueryType::ATIS => write!(f, "ATIS"),
650            ClientQueryType::PublicIP => write!(f, "IP"),
651            ClientQueryType::INF => write!(f, "INF"),
652            ClientQueryType::FlightPlan { aircraft_callsign } => {
653                write!(f, "FP:{}", aircraft_callsign)
654            }
655            ClientQueryType::RequestRelief => write!(f, "BY"),
656            ClientQueryType::CancelRequestRelief => write!(f, "HI"),
657            ClientQueryType::HelpRequest { message: None } => write!(f, "HLP"),
658            ClientQueryType::HelpRequest { message: Some(msg) } => write!(f, "HLP:{msg}"),
659            ClientQueryType::CancelHelpRequest { message: None } => write!(f, "NOHLP"),
660            ClientQueryType::CancelHelpRequest { message: Some(msg) } => write!(f, "NOHLP:{msg}"),
661            ClientQueryType::WhoHas { aircraft_callsign } => write!(f, "WH:{}", aircraft_callsign),
662            ClientQueryType::InitiateTrack { aircraft_callsign } => {
663                write!(f, "IT:{}", aircraft_callsign)
664            }
665            ClientQueryType::AcceptHandoff {
666                aircraft_callsign,
667                atc_callsign,
668            } => {
669                write!(f, "HT:{}:{}", aircraft_callsign, atc_callsign)
670            }
671            ClientQueryType::DropTrack { aircraft_callsign } => {
672                write!(f, "DR:{}", aircraft_callsign)
673            }
674            ClientQueryType::SetFinalAltitude {
675                aircraft_callsign,
676                altitude,
677            } => write!(f, "FA:{}:{}", aircraft_callsign, altitude),
678            ClientQueryType::SetTempAltitude {
679                aircraft_callsign,
680                altitude,
681            } => write!(f, "TA:{}:{}", aircraft_callsign, altitude),
682            ClientQueryType::SetBeaconCode {
683                aircraft_callsign,
684                code,
685            } => {
686                write!(f, "BC:{}:{}", aircraft_callsign, code)
687            }
688            ClientQueryType::ForceBeaconCode { code } => {
689                write!(f, "IPC:W:852:{}", code.as_bcd_format())
690            }
691            ClientQueryType::SetScratchpad {
692                aircraft_callsign,
693                contents,
694            } => {
695                write!(f, "SC:{}:{}", aircraft_callsign, contents)
696            }
697            ClientQueryType::SetVoiceType {
698                aircraft_callsign,
699                voice_capability,
700            } => {
701                write!(f, "VT:{}:{}", aircraft_callsign, voice_capability)
702            }
703            ClientQueryType::AircraftConfigurationRequest => {
704                write!(f, "ACC:{{\"request\":\"full\"}}")
705            }
706            ClientQueryType::AircraftConfigurationResponse { aircraft_config } => {
707                write!(f, "ACC:{}", aircraft_config)
708            }
709            ClientQueryType::NewATIS {
710                atis_letter,
711                surface_wind,
712                pressure,
713            } => {
714                write!(
715                    f,
716                    "NEWATIS:ATIS {}:  {} - {}",
717                    atis_letter, surface_wind, pressure
718                )
719            }
720            ClientQueryType::NewInfo { atis_letter } => {
721                write!(f, "NEWINFO:{}", atis_letter)
722            }
723            ClientQueryType::SimTime { time } => {
724                write!(f, "SIMTIME:{}", time.format("Y%m%d%H%M%S"))
725            }
726            ClientQueryType::SetGlobalData {
727                aircraft_callsign,
728                contents,
729            } => {
730                write!(f, "GD:{}:{}", aircraft_callsign, contents)
731            }
732        }
733    }
734}
735
736#[allow(unused)]
737#[derive(Clone, Debug)]
738pub enum AtisLine {
739    VoiceServer(String),
740    TextLine(String),
741    LogoffTime(Option<u16>),
742    EndMarker(usize),
743}
744impl Display for AtisLine {
745    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746        match self {
747            AtisLine::VoiceServer(voice_server) => write!(f, "V:{}", voice_server),
748            AtisLine::TextLine(text) => write!(f, "T:{}", text),
749            AtisLine::LogoffTime(Some(time)) => write!(f, "Z:{:04}z", time),
750            AtisLine::LogoffTime(None) => write!(f, "Z:z"),
751            AtisLine::EndMarker(num_lines) => write!(f, "E:{}", num_lines),
752        }
753    }
754}
755
756#[allow(unused)]
757#[derive(Clone, Debug)]
758pub enum ClientResponseType {
759    Com1Freq {
760        frequency: RadioFrequency,
761    },
762    ATIS {
763        atis_line: AtisLine,
764    },
765    RealName {
766        name: String,
767        sector_file: String,
768        rating: u8,
769    },
770    Capabilities {
771        capabilities: Vec<ClientCapability>,
772    },
773    PublicIP {
774        ip_address: String,
775    },
776    Server {
777        hostname_or_ip_address: String,
778    },
779    IsValidATC {
780        atc_callsign: String,
781        valid_atc: bool,
782    },
783}
784impl Display for ClientResponseType {
785    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
786        match self {
787            ClientResponseType::Com1Freq { frequency } => {
788                write!(f, "C?:{}", frequency.to_human_readable_string())
789            }
790            ClientResponseType::ATIS { atis_line } => write!(f, "ATIS:{}", atis_line),
791            ClientResponseType::RealName {
792                name,
793                sector_file,
794                rating,
795            } => {
796                write!(f, "RN:{}:{}:{}", name, sector_file, rating)
797            }
798            ClientResponseType::Capabilities { capabilities } => {
799                write!(f, "CAPS:")?;
800                let mut capabilities = capabilities.iter().peekable();
801                while let Some(capability) = capabilities.next() {
802                    write!(f, "{}=1", capability)?;
803                    if capabilities.peek().is_some() {
804                        write!(f, ":")?;
805                    }
806                }
807                Ok(())
808            }
809            ClientResponseType::PublicIP { ip_address } => write!(f, "IP:{}", ip_address),
810            ClientResponseType::Server {
811                hostname_or_ip_address,
812            } => write!(f, "SV:{}", hostname_or_ip_address),
813            ClientResponseType::IsValidATC {
814                atc_callsign,
815                valid_atc,
816            } => {
817                let valid = if *valid_atc { 'Y' } else { 'N' };
818                write!(f, "ATC:{}:{}", valid, atc_callsign)
819            }
820        }
821    }
822}
823
824#[allow(unused)]
825#[derive(Clone, Debug)]
826pub enum SharedStateType {
827    Version,
828    ID,
829    DI,
830    IHave {
831        aircraft_callsign: String,
832    },
833    ScratchPad {
834        aircraft_callsign: String,
835        contents: ScratchPad,
836    },
837    TempAltitude {
838        aircraft_callsign: String,
839        altitude: i32,
840    },
841    FinalAltitude {
842        aircraft_callsign: String,
843        altitude: i32,
844    },
845    VoiceType {
846        aircraft_callsign: String,
847        voice_capability: VoiceCapability,
848    },
849    BeaconCode {
850        aircraft_callsign: String,
851        code: TransponderCode,
852    },
853    HandoffCancel {
854        aircraft_callsign: String,
855    },
856    FlightStrip {
857        aircraft_callsign: String,
858        format: Option<i32>,
859        contents: Option<Vec<String>>,
860    },
861    PushToDepartureList {
862        aircraft_callsign: String,
863    },
864    PointOut {
865        aircraft_callsign: String,
866    },
867    LandLine {
868        landline_type: LandLineType,
869        landline_command: LandLineCommand,
870    },
871    GlobalData {
872        aircraft_callsign: String,
873        contents: String,
874    },
875}
876
877impl Display for SharedStateType {
878    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
879        match self {
880            SharedStateType::Version => write!(f, "VER"),
881            SharedStateType::ID => write!(f, "ID"),
882            SharedStateType::DI => write!(f, "DI"),
883            SharedStateType::IHave { aircraft_callsign } => write!(f, "IH:{}", aircraft_callsign),
884            SharedStateType::ScratchPad {
885                aircraft_callsign,
886                contents,
887            } => {
888                write!(f, "SC:{}:{}", aircraft_callsign, contents)
889            }
890            SharedStateType::TempAltitude {
891                aircraft_callsign,
892                altitude,
893            } => {
894                write!(f, "TA:{}:{}", aircraft_callsign, altitude)
895            }
896            SharedStateType::FinalAltitude {
897                aircraft_callsign,
898                altitude,
899            } => {
900                write!(f, "FA:{}:{}", aircraft_callsign, altitude)
901            }
902            SharedStateType::VoiceType {
903                aircraft_callsign,
904                voice_capability,
905            } => {
906                write!(f, "VT:{}:{}", aircraft_callsign, voice_capability)
907            }
908            SharedStateType::BeaconCode {
909                aircraft_callsign,
910                code,
911            } => write!(f, "BC:{}:{}", aircraft_callsign, code),
912            SharedStateType::HandoffCancel { aircraft_callsign } => {
913                write!(f, "HC:{}", aircraft_callsign)
914            }
915            SharedStateType::PointOut { aircraft_callsign } => {
916                write!(f, "PT:{}", aircraft_callsign)
917            }
918            SharedStateType::PushToDepartureList { aircraft_callsign } => {
919                write!(f, "DP:{}", aircraft_callsign)
920            }
921            SharedStateType::FlightStrip {
922                aircraft_callsign,
923                format,
924                contents,
925            } => {
926                write!(f, "ST:{aircraft_callsign}")?;
927                if let Some(format) = *format {
928                    write!(f, ":{format}")?;
929                }
930                if let Some(contents) = contents {
931                    for item in contents {
932                        write!(f, ":{item}")?;
933                    }
934                }
935                Ok(())
936            }
937            SharedStateType::LandLine {
938                landline_type,
939                landline_command,
940            } => match (*landline_type, *landline_command) {
941                (LandLineType::Intercom, LandLineCommand::Request { ip_address, port }) => {
942                    write!(f, "IC:{ip_address}:{port}")
943                }
944                (LandLineType::Intercom, LandLineCommand::Approve { ip_address, port }) => {
945                    write!(f, "IK:{ip_address}:{port}")
946                }
947                (LandLineType::Intercom, LandLineCommand::Reject) => write!(f, "IB"),
948                (LandLineType::Intercom, LandLineCommand::End) => write!(f, "EC"),
949
950                (LandLineType::Override, LandLineCommand::Request { ip_address, port }) => {
951                    write!(f, "OV:{ip_address}:{port}")
952                }
953                (LandLineType::Override, LandLineCommand::Approve { ip_address, port }) => {
954                    write!(f, "OK:{ip_address}:{port}")
955                }
956                (LandLineType::Override, LandLineCommand::Reject) => write!(f, "OB"),
957                (LandLineType::Override, LandLineCommand::End) => write!(f, "EO"),
958
959                (LandLineType::Monitor, LandLineCommand::Request { ip_address, port }) => {
960                    write!(f, "MN:{ip_address}:{port}")
961                }
962                (LandLineType::Monitor, LandLineCommand::Approve { ip_address, port }) => {
963                    write!(f, "MK:{ip_address}:{port}")
964                }
965                (LandLineType::Monitor, LandLineCommand::Reject) => write!(f, "MB"),
966                (LandLineType::Monitor, LandLineCommand::End) => write!(f, "EM"),
967            },
968            SharedStateType::GlobalData {
969                aircraft_callsign,
970                contents,
971            } => write!(f, "GD:{aircraft_callsign}:{contents}"),
972        }
973    }
974}
975
976#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
977pub enum LandLineType {
978    Intercom,
979    Override,
980    Monitor,
981}
982
983#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
984pub enum LandLineCommand {
985    Request { ip_address: Ipv4Addr, port: u16 },
986    Approve { ip_address: Ipv4Addr, port: u16 },
987    Reject,
988    End,
989}
990
991#[derive(Debug, Clone, Copy)]
992pub enum Operator {
993    Exactly,
994    OrLess,
995    OrGreater,
996}
997impl Display for Operator {
998    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
999        match *self {
1000            Self::Exactly => write!(f, "="),
1001            Self::OrLess => write!(f, "-"),
1002            Self::OrGreater => write!(f, "+"),
1003        }
1004    }
1005}
1006
1007#[derive(Debug, Copy, Clone)]
1008pub enum GroundState {
1009    NoState,
1010    OnFrequency,
1011    DeIcing,
1012    Startup,
1013    Pushback,
1014    Taxi,
1015    LineUp,
1016    TakeOff,
1017    TaxiIn,
1018    OnBlock,
1019}
1020impl Display for GroundState {
1021    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1022        match self {
1023            Self::NoState => write!(f, "NSTS"),
1024            Self::OnFrequency => write!(f, "ONFREQ"),
1025            Self::DeIcing => write!(f, "DE-ICE"),
1026            Self::Startup => write!(f, "STUP"),
1027            Self::Pushback => write!(f, "PUSH"),
1028            Self::Taxi => write!(f, "TAXI"),
1029            Self::LineUp => write!(f, "LINEUP"),
1030            Self::TakeOff => write!(f, "DEPA"),
1031            Self::TaxiIn => write!(f, "TXIN"),
1032            Self::OnBlock => write!(f, "PARK"),
1033        }
1034    }
1035}
1036
1037#[derive(Debug, Clone)]
1038pub enum ScratchPad {
1039    PlainTextOrDirect(String),
1040    RateOfClimbDescent(u32),
1041    Heading(u32),
1042    Speed(u32),
1043    Mach(u32),
1044    SpeedOperator(Operator),
1045    RateOfClimbDescentOperator(Operator),
1046    Stand(String),
1047    CancelledStand,
1048    ManualStand(String, String),
1049    CancelledManualStand,
1050    ClearanceReceived,
1051    ClearanceCancelled,
1052    GroundState(GroundState),
1053}
1054impl FromStr for ScratchPad {
1055    type Err = FsdMessageParseError;
1056    fn from_str(s: &str) -> Result<Self, Self::Err> {
1057        // EuroScope scratchpad use to sync clearances
1058        if let Some(Ok(h)) = s.strip_prefix('H').map(str::parse) {
1059            return Ok(Self::Heading(h));
1060        }
1061        if let Some(Ok(r)) = s.strip_prefix('R').map(str::parse) {
1062            return Ok(Self::RateOfClimbDescent(r));
1063        }
1064        if let Some(Ok(speed)) = s.strip_prefix('S').map(str::parse) {
1065            return Ok(Self::Speed(speed));
1066        }
1067        if let Some(Ok(m)) = s.strip_prefix('M').map(str::parse) {
1068            return Ok(Self::Mach(m));
1069        }
1070        if s.len() > 6 && s.starts_with("GRP/S/") {
1071            return Ok(Self::Stand(s[6..].to_string()));
1072        }
1073        if s.len() > 6 && s.starts_with("GRP/M/") {
1074            let mut split = s.split('/');
1075            return Ok(Self::ManualStand(
1076                split.next().unwrap_or("ZZZZ").to_string(),
1077                split.next().unwrap_or("").to_string(),
1078            ));
1079        }
1080        match s {
1081            // ASP/ARC operator syntax from EuroScope TopSky plugin
1082            "/ASP=/" => Ok(Self::SpeedOperator(Operator::Exactly)),
1083            "/ASP-/" => Ok(Self::SpeedOperator(Operator::OrLess)),
1084            "/ASP+/" => Ok(Self::SpeedOperator(Operator::OrGreater)),
1085            "/ARC=/" => Ok(Self::RateOfClimbDescentOperator(Operator::Exactly)),
1086            "/ARC-/" => Ok(Self::RateOfClimbDescentOperator(Operator::OrLess)),
1087            "/ARC+/" => Ok(Self::RateOfClimbDescentOperator(Operator::OrGreater)),
1088            // Stand assignment cancellations from EuroScope GroundRadar plugin
1089            "GRP/S/" => Ok(Self::CancelledStand),
1090            "GRP/M/" => Ok(Self::CancelledManualStand),
1091            // Ground states, mixed plain EuroScope and GroundRadar plugin
1092            "NSTS" => Ok(Self::GroundState(GroundState::NoState)),
1093            // TODO: Why is this a separate state?
1094            "NOSTATE" => Ok(Self::GroundState(GroundState::NoState)),
1095            "ONFREQ" => Ok(Self::GroundState(GroundState::OnFrequency)),
1096            "DE-ICE" => Ok(Self::GroundState(GroundState::DeIcing)),
1097            "STUP" | "ST-UP" => Ok(Self::GroundState(GroundState::Startup)),
1098            "PUSH" => Ok(Self::GroundState(GroundState::Pushback)),
1099            "TAXI" => Ok(Self::GroundState(GroundState::Taxi)),
1100            "LINEUP" => Ok(Self::GroundState(GroundState::LineUp)),
1101            "TXIN" => Ok(Self::GroundState(GroundState::TaxiIn)),
1102            "DEPA" => Ok(Self::GroundState(GroundState::TakeOff)),
1103            "PARK" => Ok(Self::GroundState(GroundState::OnBlock)),
1104            "CLEA" => Ok(Self::ClearanceReceived),
1105            "NOTC" => Ok(Self::ClearanceCancelled),
1106            text => Ok(ScratchPad::PlainTextOrDirect(text.to_string())),
1107        }
1108    }
1109}
1110impl Display for ScratchPad {
1111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1112        match self {
1113            Self::RateOfClimbDescent(r) => write!(f, "R{r}"),
1114            Self::Heading(h) => write!(f, "H{h}"),
1115            Self::Speed(speed) => write!(f, "S{speed}"),
1116            Self::Mach(m) => write!(f, "{m}"),
1117            Self::SpeedOperator(op) => write!(f, "/ASP{op}/"),
1118            Self::RateOfClimbDescentOperator(op) => write!(f, "/ARC{op}/"),
1119            Self::PlainTextOrDirect(text) => write!(f, "{text}"),
1120            Self::Stand(stand) => write!(f, "GRP/S/{stand}"),
1121            Self::CancelledStand => write!(f, "GRP/S/"),
1122            Self::ManualStand(icao, stand) => write!(f, "GRP/M/{icao}/{stand}"),
1123            Self::CancelledManualStand => write!(f, "GRP/M"),
1124            Self::GroundState(gs) => gs.fmt(f),
1125            Self::ClearanceReceived => write!(f, "CLEA"),
1126            Self::ClearanceCancelled => write!(f, "NOTC"),
1127        }
1128    }
1129}
1130
1131#[derive(Debug, Clone, Copy)]
1132pub enum VoiceCapability {
1133    Unknown,
1134    Voice,
1135    Text,
1136    Receive,
1137}
1138impl FromStr for VoiceCapability {
1139    type Err = FsdMessageParseError;
1140    fn from_str(s: &str) -> Result<Self, Self::Err> {
1141        if s.is_empty() {
1142            return Ok(VoiceCapability::Unknown);
1143        }
1144        let s = s.to_lowercase();
1145        match s.as_str() {
1146            "v" => Ok(VoiceCapability::Voice),
1147            "t" => Ok(VoiceCapability::Text),
1148            "r" => Ok(VoiceCapability::Receive),
1149            _ => Err(FsdMessageParseError::InvalidVoiceCapability(s)),
1150        }
1151    }
1152}
1153impl Display for VoiceCapability {
1154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1155        match *self {
1156            VoiceCapability::Unknown => write!(f, ""),
1157            VoiceCapability::Voice => write!(f, "v"),
1158            VoiceCapability::Text => write!(f, "t"),
1159            VoiceCapability::Receive => write!(f, "r"),
1160        }
1161    }
1162}