1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use nom::error::VerboseError;
use nom::IResult;
use std::convert::TryFrom;
use thiserror::Error;
use tinyvec::ArrayVec;

mod parser;

pub type Res<T, U> = IResult<T, U, VerboseError<T>>;

pub enum IncomingMessage<'a> {
    RegistrationResult(RegistrationResult<'a>),
    RealtimeUpdate(RealtimeUpdate<'a>),
    RealtimeCarUpdate(RealtimeCarUpdate),
}

impl<'a> IncomingMessage<'a> {
    pub fn parse(input: &'a [u8]) -> Res<&[u8], IncomingMessage> {
        parser::parse(input)
    }
}

/// Describes a response to the initial broadcast client connection request.
#[derive(Clone, Debug, PartialEq)]
pub struct RegistrationResult<'a> {
    pub connection_id: u32,
    pub connection_success: bool,
    pub read_only: bool,
    pub error_message: &'a str,
}

#[derive(Debug, Error)]
pub enum DecodeError {
    #[error("Unrecognised session type `{0}`")]
    UnknownSessionType(u8),
    #[error("Unrecognised session phase `{0}`")]
    UnknownSessionPhase(u8),
    #[error("Unrecognised car location `{0}`")]
    UnknownCarLocation(u8),
}

#[derive(Debug, PartialEq)]
pub enum SessionType {
    Practice,
    Qualifying,
    Superpole,
    Race,
    Hotlap,
    Hotstint,
    HotlapSuperpole,
    Replay,
}

impl TryFrom<u8> for SessionType {
    type Error = DecodeError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(SessionType::Practice),
            4 => Ok(SessionType::Qualifying),
            9 => Ok(SessionType::Superpole),
            10 => Ok(SessionType::Race),
            11 => Ok(SessionType::Hotlap),
            12 => Ok(SessionType::Hotstint),
            13 => Ok(SessionType::HotlapSuperpole),
            14 => Ok(SessionType::Replay),
            x => Err(DecodeError::UnknownSessionType(x)),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum SessionPhase {
    None,
    Starting,
    PreFormation,
    FormationLap,
    PreSession,
    Session,
    SessionOver,
    PostSession,
    ResultUi,
}

impl TryFrom<u8> for SessionPhase {
    type Error = DecodeError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(SessionPhase::None),
            1 => Ok(SessionPhase::Starting),
            2 => Ok(SessionPhase::PreFormation),
            3 => Ok(SessionPhase::FormationLap),
            4 => Ok(SessionPhase::PreSession),
            5 => Ok(SessionPhase::Session),
            6 => Ok(SessionPhase::SessionOver),
            7 => Ok(SessionPhase::PostSession),
            8 => Ok(SessionPhase::ResultUi),
            x => Err(DecodeError::UnknownSessionPhase(x)),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum CarLocation {
    None,
    Track,
    Pitlane,
    PitEntry,
    PitExit,
}

impl TryFrom<u8> for CarLocation {
    type Error = DecodeError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(CarLocation::None),
            1 => Ok(CarLocation::Track),
            2 => Ok(CarLocation::Pitlane),
            3 => Ok(CarLocation::PitEntry),
            4 => Ok(CarLocation::PitExit),
            x => Err(DecodeError::UnknownCarLocation(x)),
        }
    }
}

#[derive(Debug)]
pub struct Lap {
    pub lap_time_ms: i32,
    pub car_id: u16,
    pub driver_id: u16,
    /// Sector split times, in milliseconds. This field is not populated for partially completed laps.
    pub splits: ArrayVec<[i32; 3]>,
    pub is_invalid: bool,
    pub is_valid_for_best: bool,
    pub is_out_lap: bool,
    pub is_in_lap: bool,
}

#[derive(Debug)]
pub struct ReplayInfo {
    pub session_time: f32,
    pub remaining_time: f32,
    pub focused_car_index: u32,
}

#[derive(Debug)]
pub struct RealtimeUpdate<'a> {
    /// The event index, starts at 0 when connecting, and increments with each new race weekend.
    pub event_index: u16,
    /// The session index, will start at 0 when connecting, even if a previous session has already
    /// taken place in this event.
    pub session_index: u16,
    pub session_type: SessionType,
    pub session_phase: SessionPhase,
    /// Session time in milliseconds since the green flag
    pub session_time: f32,
    /// Time in milliseconds remaining before the end of the session
    pub session_end_time: f32,
    pub focused_car_index: u32,
    pub active_camera_set: &'a str,
    pub active_camera: &'a str,
    pub current_hud_page: &'a str,
    pub replay_info: Option<ReplayInfo>,
    pub time_of_day: f32,
    pub ambient_temp: i8,
    pub track_temp: i8,
    pub clouds: u8,
    pub rain_level: u8,
    pub wetness: u8,
    pub best_session_lap: Lap,
}

#[derive(Debug)]
pub struct RealtimeCarUpdate {
    pub id: u16,
    pub driver_id: u16,
    pub driver_count: u8,
    pub gear: i8,
    pub world_pos_x: f32,
    pub world_pos_y: f32,
    pub yaw: f32,
    pub car_location: CarLocation,
    pub speed_kph: u16,
    pub position: u16,
    pub cup_position: u16,
    pub track_position: u16,
    pub spline_position: f32,
    pub laps: u16,
    pub delta: i32,
    pub best_session_lap: Lap,
    pub last_lap: Lap,
    /// Lap data for the current lap, note that sector times are not populated for this field.
    pub current_lap: Lap,
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}