f1_api/nineteen/
session.rs

1//! Decoder for session packet sent by F1 2019
2//!
3//! The session packets by F1 2018 and F1 2019 differ only in their packet headers, the rest of the
4//! packet format is identical.
5
6use std::io::{Cursor, Error, ErrorKind};
7use std::time::Duration;
8
9use bytes::{Buf, BytesMut};
10
11use crate::nineteen::flag::decode_flag;
12use crate::nineteen::header::decode_header;
13use crate::packet::ensure_packet_size;
14use crate::packet::session::{
15    Formula, MarshalZone, SafetyCar, Session, SessionPacket, Track, Weather,
16};
17
18/// Size of the session packet in F1 2019
19pub const PACKET_SIZE: usize = 149;
20
21/// Decode a session packet sent by F1 2019
22///
23/// The session packets by F1 2018 and F1 2019 differ only in their packet headers, the rest of the
24/// packet format is identical.
25pub fn decode_session(cursor: &mut Cursor<&mut BytesMut>) -> Result<SessionPacket, Error> {
26    ensure_packet_size(PACKET_SIZE, cursor)?;
27
28    let header = decode_header(cursor)?;
29
30    let weather = decode_weather(cursor)?;
31    let track_temperature = cursor.get_i8();
32    let air_temperature = cursor.get_i8();
33    let total_laps = cursor.get_u8();
34    let track_length = cursor.get_u16_le();
35    let session_type = decode_session_type(cursor)?;
36    let track = decode_track(cursor)?;
37    let formula = decode_formula(cursor)?;
38    let time_left = Duration::from_secs(cursor.get_u16_le() as u64);
39    let duration = Duration::from_secs(cursor.get_u16_le() as u64);
40    let pit_speed_limit = cursor.get_u8();
41    let game_paused = cursor.get_u8() > 0;
42    let is_spectating = cursor.get_u8() > 0;
43    let spectator_car_index = cursor.get_u8();
44    let sli_pro_support = cursor.get_u8() > 0;
45
46    let marshal_zone_count = cursor.get_u8();
47    let mut marshal_zones = Vec::with_capacity(marshal_zone_count as usize);
48
49    for _ in 0..marshal_zone_count {
50        marshal_zones.push(MarshalZone::new(cursor.get_f32_le(), decode_flag(cursor)?));
51    }
52
53    let safety_car = decode_safety_car(cursor)?;
54    let network_session = cursor.get_u8() > 0;
55
56    Ok(SessionPacket::new(
57        header,
58        weather,
59        track_temperature,
60        air_temperature,
61        total_laps,
62        track_length,
63        session_type,
64        track,
65        formula,
66        time_left,
67        duration,
68        pit_speed_limit,
69        game_paused,
70        is_spectating,
71        spectator_car_index,
72        sli_pro_support,
73        marshal_zones,
74        safety_car,
75        network_session,
76    ))
77}
78
79fn decode_weather(cursor: &mut Cursor<&mut BytesMut>) -> Result<Weather, Error> {
80    let value = cursor.get_u8();
81
82    match value {
83        0 => Ok(Weather::Clear),
84        1 => Ok(Weather::LightCloud),
85        2 => Ok(Weather::Overcast),
86        3 => Ok(Weather::LightRain),
87        4 => Ok(Weather::HeavyRain),
88        5 => Ok(Weather::Storm),
89        _ => Err(Error::new(
90            ErrorKind::InvalidData,
91            "Failed to decode weather.",
92        )),
93    }
94}
95
96fn decode_session_type(cursor: &mut Cursor<&mut BytesMut>) -> Result<Session, Error> {
97    let value = cursor.get_u8();
98
99    match value {
100        0 => Ok(Session::Unknown),
101        1 => Ok(Session::P1),
102        2 => Ok(Session::P2),
103        3 => Ok(Session::P3),
104        4 => Ok(Session::ShortPractice),
105        5 => Ok(Session::Q1),
106        6 => Ok(Session::Q2),
107        7 => Ok(Session::Q3),
108        8 => Ok(Session::ShortQualifying),
109        9 => Ok(Session::OneShotQualifying),
110        10 => Ok(Session::Race),
111        11 => Ok(Session::Race2),
112        12 => Ok(Session::TimeTrial),
113        _ => Err(Error::new(
114            ErrorKind::InvalidData,
115            "Failed to decode session.",
116        )),
117    }
118}
119
120fn decode_track(cursor: &mut Cursor<&mut BytesMut>) -> Result<Track, Error> {
121    let value = cursor.get_i8();
122
123    match value {
124        -1 => Ok(Track::Unknown),
125        0 => Ok(Track::Melbourne),
126        1 => Ok(Track::PaulRicard),
127        2 => Ok(Track::Shanghai),
128        3 => Ok(Track::Bahrain),
129        4 => Ok(Track::Catalunya),
130        5 => Ok(Track::Monaco),
131        6 => Ok(Track::Montreal),
132        7 => Ok(Track::Silverstone),
133        8 => Ok(Track::Hockenheim),
134        9 => Ok(Track::Hungaroring),
135        10 => Ok(Track::Spa),
136        11 => Ok(Track::Monza),
137        12 => Ok(Track::Singapore),
138        13 => Ok(Track::Suzuka),
139        14 => Ok(Track::AbuDhabi),
140        15 => Ok(Track::Texas),
141        16 => Ok(Track::Brazil),
142        17 => Ok(Track::Austria),
143        18 => Ok(Track::Sochi),
144        19 => Ok(Track::Mexico),
145        20 => Ok(Track::Azerbaijan),
146        21 => Ok(Track::BahrainShort),
147        22 => Ok(Track::SilverstoneShort),
148        23 => Ok(Track::TexasShort),
149        24 => Ok(Track::SuzukaShort),
150        _ => Err(Error::new(
151            ErrorKind::InvalidData,
152            "Failed to decode track.",
153        )),
154    }
155}
156
157fn decode_formula(cursor: &mut Cursor<&mut BytesMut>) -> Result<Formula, Error> {
158    let value = cursor.get_u8();
159
160    match value {
161        0 => Ok(Formula::ModernF1),
162        1 => Ok(Formula::ClassicF1),
163        2 => Ok(Formula::F2),
164        3 => Ok(Formula::GenericF1),
165        _ => Err(Error::new(
166            ErrorKind::InvalidData,
167            "Failed to decode formula.",
168        )),
169    }
170}
171
172fn decode_safety_car(cursor: &mut Cursor<&mut BytesMut>) -> Result<SafetyCar, Error> {
173    let value = cursor.get_u8();
174
175    match value {
176        0 => Ok(SafetyCar::None),
177        1 => Ok(SafetyCar::Full),
178        2 => Ok(SafetyCar::Virtual),
179        _ => Err(Error::new(
180            ErrorKind::InvalidData,
181            "Failed to decode safety car.",
182        )),
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use std::io::Cursor;
189
190    use bytes::{BufMut, BytesMut};
191
192    use crate::nineteen::session::{decode_session, PACKET_SIZE};
193    use crate::packet::session::{Formula, SafetyCar, Session, Track, Weather};
194
195    fn put_packet_header(mut bytes: BytesMut) -> BytesMut {
196        bytes.put_u16_le(2019);
197        bytes.put_u8(1);
198        bytes.put_u8(2);
199        bytes.put_u8(3);
200        bytes.put_u8(1);
201        bytes.put_u64_le(u64::max_value());
202        bytes.put_f32_le(1.0);
203        bytes.put_u32_le(u32::max_value());
204        bytes.put_u8(0);
205
206        bytes
207    }
208
209    #[test]
210    fn decode_session_with_error() {
211        let mut bytes = BytesMut::with_capacity(0);
212        let mut cursor = Cursor::new(&mut bytes);
213
214        let packet = decode_session(&mut cursor);
215        assert!(packet.is_err());
216    }
217
218    #[test]
219    fn decode_session_with_success() {
220        let mut bytes = BytesMut::with_capacity(PACKET_SIZE);
221        bytes = put_packet_header(bytes);
222
223        bytes.put_u8(1);
224        bytes.put_i8(2);
225        bytes.put_i8(3);
226        bytes.put_u8(4);
227        bytes.put_u16_le(5);
228        bytes.put_u8(6);
229        bytes.put_i8(7);
230        bytes.put_u8(2);
231        bytes.put_u16_le(9);
232        bytes.put_u16_le(10);
233        bytes.put_u8(11);
234        bytes.put_u8(1);
235        bytes.put_u8(1);
236        bytes.put_u8(14);
237        bytes.put_u8(1);
238        bytes.put_u8(21);
239
240        for i in 0..21 {
241            bytes.put_f32_le(i as f32);
242            bytes.put_i8((i % 6) - 1);
243        }
244
245        bytes.put_u8(1);
246        bytes.put_u8(1);
247
248        let mut cursor = Cursor::new(&mut bytes);
249
250        let packet = decode_session(&mut cursor).unwrap();
251
252        assert_eq!(Weather::LightCloud, packet.weather());
253        assert_eq!(2, packet.track_temperature());
254        assert_eq!(3, packet.air_temperature());
255        assert_eq!(4, packet.total_laps());
256        assert_eq!(5, packet.track_length());
257        assert_eq!(Session::Q2, packet.session_type());
258        assert_eq!(Track::Silverstone, packet.track());
259        assert_eq!(Formula::F2, packet.formula());
260        assert_eq!(9, packet.time_left().as_secs());
261        assert_eq!(10, packet.duration().as_secs());
262        assert_eq!(11, packet.pit_speed_limit());
263        assert!(packet.game_paused());
264        assert!(packet.is_spectating());
265        assert_eq!(14, packet.spectator_car_index());
266        assert!(packet.sli_pro_support());
267        assert_eq!(21, packet.marshal_zones().len());
268        assert_eq!(SafetyCar::Full, packet.safety_car());
269        assert!(packet.network_session());
270    }
271}