1use alloc::borrow::ToOwned as _;
4use alloc::string::String;
5use core::str::FromStr;
6use core::{cmp, fmt, str};
7
8use hashbrown::HashMap;
9use time::PrimitiveDateTime;
10
11use crate::frame::gps::{GpsFrameDef, GpsFrameDefBuilder};
12use crate::frame::gps_home::{GpsHomeFrameDef, GpsHomeFrameDefBuilder};
13use crate::frame::main::{MainFrameDef, MainFrameDefBuilder};
14use crate::frame::slow::{SlowFrameDef, SlowFrameDefBuilder};
15use crate::frame::{is_frame_def_header, parse_frame_def_header, DataFrameKind};
16use crate::parser::{InternalError, InternalResult};
17use crate::predictor::Predictor;
18use crate::{DataParser, FilterSet, Reader, Unit};
19
20include_generated!("debug_mode");
21include_generated!("disabled_fields");
22include_generated!("features");
23include_generated!("pwm_protocol");
24
25pub type ParseResult<T> = Result<T, ParseError>;
26
27#[derive(Debug, Clone)]
29#[cfg_attr(feature = "_serde", derive(serde::Serialize))]
30pub enum ParseError {
31 UnsupportedDataVersion,
34 InvalidFirmware(String),
37 UnsupportedFirmwareVersion(Firmware),
39 InvalidHeader { header: String, value: String },
41 MissingHeader,
44 IncompleteHeaders,
46 MissingField { frame: DataFrameKind, field: String },
48 MalformedFrameDef(DataFrameKind),
50}
51
52impl fmt::Display for ParseError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::UnsupportedDataVersion => write!(f, "unsupported or invalid data version"),
56 Self::InvalidFirmware(firmware) => write!(f, "could not parse firmware: `{firmware}`"),
57 Self::UnsupportedFirmwareVersion(firmware) => {
58 let name = firmware.name();
59 let version = firmware.version();
60 write!(f, "logs from {name} v{version} are not supported")
61 }
62 Self::InvalidHeader { header, value } => {
63 write!(f, "invalid value for header `{header}`: `{value}`")
64 }
65 Self::MissingHeader => {
66 write!(f, "one or more headers required for parsing are missing")
67 }
68 Self::IncompleteHeaders => write!(f, "end of file found before data section"),
69 Self::MissingField { frame, field } => {
70 write!(f, "missing field `{field}` in `{frame}` frame definition")
71 }
72 Self::MalformedFrameDef(frame) => write!(f, "malformed {frame} frame definition"),
73 }
74 }
75}
76
77impl core::error::Error for ParseError {}
78
79#[derive(Debug, Clone)]
81#[non_exhaustive]
82pub struct Headers<'data> {
83 data: Reader<'data>,
84
85 main_frame_def: MainFrameDef<'data>,
86 slow_frame_def: SlowFrameDef<'data>,
87 gps_frame_def: Option<GpsFrameDef<'data>>,
88 gps_home_frame_def: Option<GpsHomeFrameDef<'data>>,
89
90 firmware_revision: &'data str,
91 pub(crate) internal_firmware: InternalFirmware,
92 firmware: Firmware,
93 firmware_date: Option<&'data str>,
94 board_info: Option<&'data str>,
95 craft_name: Option<&'data str>,
96
97 debug_mode: DebugMode,
98 disabled_fields: DisabledFields,
99 features: FeatureSet,
100 pwm_protocol: PwmProtocol,
101
102 pub(crate) vbat_reference: Option<u16>,
104 pub(crate) acceleration_1g: Option<u16>,
106 pub(crate) gyro_scale: Option<f32>,
108
109 pub(crate) min_throttle: Option<u16>,
110 pub(crate) motor_output_range: Option<MotorOutputRange>,
111
112 unknown: HashMap<&'data str, &'data str>,
113}
114
115impl<'data> Headers<'data> {
116 pub(crate) fn parse(data: &'data [u8]) -> ParseResult<Self> {
123 let mut data = Reader::new(data);
124
125 let product = data.read_line();
127 debug_assert_eq!(crate::MARKER.strip_suffix(b"\n"), product);
128 let data_version = data.read_line();
129 if !matches!(data_version, Some(b"H Data version:2")) {
130 return Err(ParseError::UnsupportedDataVersion);
131 }
132
133 let mut state = State::new();
134
135 loop {
136 if data.peek() != Some(b'H') {
137 break;
138 }
139
140 let restore = data.get_restore_point();
141 let (name, value) = match parse_header(&mut data) {
142 Ok(x) => x,
143 Err(InternalError::Retry) => {
144 tracing::debug!("found corrupted header");
145 data.restore(restore);
146 break;
147 }
148 Err(InternalError::Eof) => return Err(ParseError::IncompleteHeaders),
149 };
150
151 if !state.update(name, value) {
152 return Err(ParseError::InvalidHeader {
153 header: name.to_owned(),
154 value: value.to_owned(),
155 });
156 }
157 }
158
159 state.finish(data)
160 }
161
162 fn validate(&self) -> ParseResult<()> {
163 let has_accel = self.acceleration_1g.is_some();
164 let has_min_throttle = self.min_throttle.is_some();
165 let motor_0 = self.main_frame_def.index_motor_0;
167 let has_vbat_ref = self.vbat_reference.is_some();
168 let has_min_motor = self.motor_output_range.is_some();
169 let has_gps_home = self.gps_home_frame_def.is_some();
170
171 let predictor = |frame, field, predictor, index| {
172 let ok = match predictor {
173 Predictor::MinThrottle => has_min_throttle,
174 Predictor::Motor0 => motor_0.is_some() && index > motor_0.unwrap(),
175 Predictor::HomeLat | Predictor::HomeLon => has_gps_home,
176 Predictor::VBatReference => has_vbat_ref,
177 Predictor::MinMotor => has_min_motor,
178 Predictor::Zero
179 | Predictor::Previous
180 | Predictor::StraightLine
181 | Predictor::Average2
182 | Predictor::Increment
183 | Predictor::FifteenHundred
184 | Predictor::LastMainFrameTime => true,
185 };
186
187 if ok {
188 Ok(())
189 } else {
190 tracing::error!(field, ?predictor, "bad predictor");
191 Err(ParseError::MalformedFrameDef(frame))
192 }
193 };
194
195 let unit = |frame, field, unit| {
196 if unit == Unit::Acceleration && !has_accel {
197 tracing::error!(field, ?unit, "bad unit");
198 Err(ParseError::MalformedFrameDef(frame))
199 } else {
200 Ok(())
201 }
202 };
203
204 self.main_frame_def.validate(predictor, unit)?;
205 self.slow_frame_def.validate(predictor, unit)?;
206
207 if let Some(ref def) = self.gps_frame_def {
208 def.validate(predictor, unit)?;
209 }
210
211 if let Some(ref def) = self.gps_home_frame_def {
212 def.validate(predictor, unit)?;
213 }
214
215 Ok(())
216 }
217}
218
219impl<'data> Headers<'data> {
220 pub fn data_parser<'headers>(&'headers self) -> DataParser<'data, 'headers> {
222 DataParser::new(self.data.clone(), self, &FilterSet::default())
223 }
224
225 pub fn data_parser_with_filters<'headers>(
226 &'headers self,
227 filters: &FilterSet,
228 ) -> DataParser<'data, 'headers> {
229 DataParser::new(self.data.clone(), self, filters)
230 }
231}
232
233impl<'data> Headers<'data> {
235 #[inline]
236 pub fn main_frame_def(&self) -> &MainFrameDef<'data> {
237 &self.main_frame_def
238 }
239
240 #[inline]
241 pub fn slow_frame_def(&self) -> &SlowFrameDef<'data> {
242 &self.slow_frame_def
243 }
244
245 #[inline]
246 pub fn gps_frame_def(&self) -> Option<&GpsFrameDef<'data>> {
247 self.gps_frame_def.as_ref()
248 }
249
250 #[inline]
251 pub(crate) fn gps_home_frame_def(&self) -> Option<&GpsHomeFrameDef<'data>> {
252 self.gps_home_frame_def.as_ref()
253 }
254
255 #[inline]
259 pub fn firmware_revision(&self) -> &'data str {
260 self.firmware_revision
261 }
262
263 #[inline]
265 pub fn firmware(&self) -> Firmware {
266 self.firmware
267 }
268
269 pub fn firmware_date(&self) -> Option<Result<PrimitiveDateTime, &'data str>> {
271 let format = time::macros::format_description!(
272 "[month repr:short case_sensitive:false] [day padding:space] [year] [hour \
273 repr:24]:[minute]:[second]"
274 );
275 self.firmware_date
276 .map(|date| PrimitiveDateTime::parse(date, &format).map_err(|_| date))
277 }
278
279 #[inline]
281 pub fn board_info(&self) -> Option<&'data str> {
282 self.board_info
283 }
284
285 #[inline]
287 pub fn craft_name(&self) -> Option<&'data str> {
288 self.craft_name
289 }
290
291 #[inline]
292 pub fn debug_mode(&self) -> DebugMode {
293 self.debug_mode
294 }
295
296 #[inline]
297 pub fn disabled_fields(&self) -> DisabledFields {
298 self.disabled_fields
299 }
300
301 #[inline]
302 pub fn features(&self) -> FeatureSet {
303 self.features
304 }
305
306 #[inline]
307 pub fn pwm_protocol(&self) -> PwmProtocol {
308 self.pwm_protocol
309 }
310
311 #[inline]
313 pub fn unknown(&self) -> &HashMap<&'data str, &'data str> {
314 &self.unknown
315 }
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
324#[cfg_attr(feature = "_serde", derive(serde::Serialize))]
325pub enum Firmware {
326 Betaflight(FirmwareVersion),
328 Inav(FirmwareVersion),
330}
331
332impl Firmware {
333 pub const fn name(&self) -> &'static str {
334 match self {
335 Firmware::Betaflight(_) => "Betaflight",
336 Firmware::Inav(_) => "INAV",
337 }
338 }
339
340 pub const fn version(&self) -> FirmwareVersion {
341 let (Self::Betaflight(version) | Self::Inav(version)) = self;
342 *version
343 }
344
345 fn parse(firmware_revision: &str) -> Result<Self, ParseError> {
346 let invalid_fw = || Err(ParseError::InvalidFirmware(firmware_revision.to_owned()));
347
348 let mut iter = firmware_revision.split(' ');
349
350 let kind = iter.next().map(str::to_ascii_lowercase);
351 let Some(version) = iter.next().and_then(FirmwareVersion::parse) else {
352 return invalid_fw();
353 };
354
355 let (fw, is_supported) = match kind.as_deref() {
356 Some("betaflight") => (
357 Firmware::Betaflight(version),
358 crate::BETAFLIGHT_SUPPORT.contains(&version),
359 ),
360 Some("inav") => (
361 Firmware::Inav(version),
362 crate::INAV_SUPPORT.contains(&version),
363 ),
364 Some("emuflight") => {
365 tracing::error!("EmuFlight is not supported");
366 return invalid_fw();
367 }
368 _ => {
369 tracing::error!("Could not parse firmware revision");
370 return invalid_fw();
371 }
372 };
373
374 if is_supported {
375 Ok(fw)
376 } else {
377 Err(ParseError::UnsupportedFirmwareVersion(fw))
378 }
379 }
380}
381
382impl PartialOrd for Firmware {
383 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
384 match (self, other) {
385 (Firmware::Betaflight(fw_self), Firmware::Betaflight(fw_other))
386 | (Firmware::Inav(fw_self), Firmware::Inav(fw_other)) => fw_self.partial_cmp(fw_other),
387 _ => None,
388 }
389 }
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
393pub struct FirmwareVersion {
394 pub major: u8,
395 pub minor: u8,
396 pub patch: u8,
397}
398
399impl FirmwareVersion {
400 pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
401 Self {
402 major,
403 minor,
404 patch,
405 }
406 }
407
408 fn parse(s: &str) -> Option<Self> {
409 let mut components = s.splitn(3, '.').map(|s| s.parse().ok());
410
411 let major = components.next()??;
412 let minor = components.next()??;
413 let patch = components.next()??;
414
415 Some(Self {
416 major,
417 minor,
418 patch,
419 })
420 }
421}
422
423impl fmt::Display for FirmwareVersion {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
426 }
427}
428
429#[cfg(feature = "_serde")]
430impl serde::Serialize for FirmwareVersion {
431 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
432 where
433 S: serde::Serializer,
434 {
435 use alloc::string::ToString as _;
436 serializer.serialize_str(&self.to_string())
437 }
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
441pub(crate) enum InternalFirmware {
442 Betaflight4_2,
443 Betaflight4_3,
444 Betaflight4_4,
445 Betaflight4_5,
446 Inav5,
447 Inav6,
448 Inav7,
449 Inav8,
450}
451
452impl InternalFirmware {
453 pub(crate) const fn is_betaflight(self) -> bool {
454 match self {
455 Self::Betaflight4_2
456 | Self::Betaflight4_3
457 | Self::Betaflight4_4
458 | Self::Betaflight4_5 => true,
459 Self::Inav5 | Self::Inav6 | Self::Inav7 | Self::Inav8 => false,
460 }
461 }
462
463 #[expect(unused)]
464 pub(crate) const fn is_inav(self) -> bool {
465 !self.is_betaflight()
467 }
468}
469
470impl From<Firmware> for InternalFirmware {
471 fn from(fw: Firmware) -> Self {
472 #[expect(clippy::wildcard_enum_match_arm)]
473 match fw {
474 Firmware::Betaflight(FirmwareVersion {
475 major: 4, minor: 2, ..
476 }) => Self::Betaflight4_2,
477 Firmware::Betaflight(FirmwareVersion {
478 major: 4, minor: 3, ..
479 }) => Self::Betaflight4_3,
480 Firmware::Betaflight(FirmwareVersion {
481 major: 4, minor: 4, ..
482 }) => Self::Betaflight4_4,
483 Firmware::Betaflight(FirmwareVersion {
484 major: 4, minor: 5, ..
485 }) => Self::Betaflight4_5,
486 Firmware::Inav(FirmwareVersion { major: 5, .. }) => Self::Inav5,
487 Firmware::Inav(FirmwareVersion { major: 6, .. }) => Self::Inav6,
488 Firmware::Inav(FirmwareVersion { major: 7, .. }) => Self::Inav7,
489 Firmware::Inav(FirmwareVersion { major: 8, .. }) => Self::Inav8,
490 _ => unreachable!(),
491 }
492 }
493}
494
495impl PartialOrd for InternalFirmware {
496 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
497 if self.is_betaflight() != other.is_betaflight() {
498 return None;
499 }
500
501 Some((*self as u8).cmp(&(*other as u8)))
502 }
503}
504
505#[derive(Debug, Clone, Copy)]
506pub(crate) struct MotorOutputRange {
507 pub(crate) min: u16,
508 #[expect(dead_code)]
509 pub(crate) max: u16,
510}
511
512impl MotorOutputRange {
513 pub(crate) fn from_str(s: &str) -> Option<Self> {
514 let (min, max) = s.split_once(',')?;
515 let min = min.parse().ok()?;
516 let max = max.parse().ok()?;
517 Some(Self { min, max })
518 }
519}
520
521#[derive(Debug)]
522struct RawHeaderValue<'data, T> {
523 header: &'data str,
524 raw: &'data str,
525 value: T,
526}
527
528impl<T> RawHeaderValue<'_, T> {
529 fn invalid_header_error(&self) -> ParseError {
530 ParseError::InvalidHeader {
531 header: self.header.to_owned(),
532 value: self.raw.to_owned(),
533 }
534 }
535}
536
537impl<'data, T: FromStr> RawHeaderValue<'data, T> {
538 fn parse(header: &'data str, raw: &'data str) -> Result<Self, <T as FromStr>::Err> {
539 Ok(Self {
540 header,
541 raw,
542 value: raw.parse()?,
543 })
544 }
545}
546
547#[derive(Debug)]
548struct State<'data> {
549 main_frames: MainFrameDefBuilder<'data>,
550 slow_frames: SlowFrameDefBuilder<'data>,
551 gps_frames: GpsFrameDefBuilder<'data>,
552 gps_home_frames: GpsHomeFrameDefBuilder<'data>,
553
554 firmware_revision: Option<&'data str>,
555 firmware_date: Option<&'data str>,
556 firmware_kind: Option<&'data str>,
557 board_info: Option<&'data str>,
558 craft_name: Option<&'data str>,
559
560 debug_mode: Option<RawHeaderValue<'data, u32>>,
561 disabled_fields: u32,
562 features: u32,
563 pwm_protocol: Option<RawHeaderValue<'data, u32>>,
564
565 vbat_reference: Option<u16>,
566 acceleration_1g: Option<u16>,
567 gyro_scale: Option<f32>,
568
569 min_throttle: Option<u16>,
570 motor_output_range: Option<MotorOutputRange>,
571
572 unknown: HashMap<&'data str, &'data str>,
573}
574
575impl<'data> State<'data> {
576 fn new() -> Self {
577 Self {
578 main_frames: MainFrameDef::builder(),
579 slow_frames: SlowFrameDef::builder(),
580 gps_frames: GpsFrameDef::builder(),
581 gps_home_frames: GpsHomeFrameDef::builder(),
582
583 firmware_revision: None,
584 firmware_date: None,
585 firmware_kind: None,
586 board_info: None,
587 craft_name: None,
588
589 debug_mode: None,
590 disabled_fields: 0,
591 features: 0,
592 pwm_protocol: None,
593
594 vbat_reference: None,
595 acceleration_1g: None,
596 gyro_scale: None,
597
598 min_throttle: None,
599 motor_output_range: None,
600
601 unknown: HashMap::new(),
602 }
603 }
604
605 fn update(&mut self, header: &'data str, value: &'data str) -> bool {
607 (|| -> Result<(), ()> {
609 match header {
610 "Firmware revision" => self.firmware_revision = Some(value),
611 "Firmware date" => self.firmware_date = Some(value),
612 "Firmware type" => self.firmware_kind = Some(value),
613 "Board information" => self.board_info = Some(value),
614 "Craft name" => self.craft_name = Some(value),
615
616 "debug_mode" => {
617 let debug_mode = RawHeaderValue::parse(header, value).map_err(|_| ())?;
618 self.debug_mode = Some(debug_mode);
619 }
620 "fields_disabled_mask" => self.disabled_fields = value.parse().map_err(|_| ())?,
621 "features" => self.features = value.parse::<i32>().map_err(|_| ())?.cast_unsigned(),
622 "motor_pwm_protocol" => {
623 let protocol = RawHeaderValue::parse(header, value).map_err(|_| ())?;
624 self.pwm_protocol = Some(protocol);
625 }
626
627 "vbatref" => {
628 let vbat_reference = value.parse().map_err(|_| ())?;
629 self.vbat_reference = Some(vbat_reference);
630 }
631 "acc_1G" => {
632 let one_g = value.parse().map_err(|_| ())?;
633 self.acceleration_1g = Some(one_g);
634 }
635 "gyro.scale" | "gyro_scale" => {
636 let scale = if let Some(hex) = value.strip_prefix("0x") {
637 u32::from_str_radix(hex, 16).map_err(|_| ())?
638 } else {
639 value.parse().map_err(|_| ())?
640 };
641
642 let scale = f32::from_bits(scale);
643 self.gyro_scale = Some(scale.to_radians());
644 }
645 "minthrottle" => {
646 let min_throttle = value.parse().map_err(|_| ())?;
647 self.min_throttle = Some(min_throttle);
648 }
649 "motorOutput" => {
650 let range = MotorOutputRange::from_str(value).ok_or(())?;
651 self.motor_output_range = Some(range);
652 }
653
654 _ if is_frame_def_header(header) => {
655 let (frame_kind, property) = parse_frame_def_header(header).unwrap();
656
657 match frame_kind {
658 DataFrameKind::Inter | DataFrameKind::Intra => {
659 self.main_frames.update(frame_kind, property, value);
660 }
661 DataFrameKind::Slow => self.slow_frames.update(property, value),
662 DataFrameKind::Gps => self.gps_frames.update(property, value),
663 DataFrameKind::GpsHome => self.gps_home_frames.update(property, value),
664 }
665 }
666
667 "vbatscale" | "vbat_scale" | "currentMeter" | "currentSensor" => {}
669
670 header => {
671 tracing::debug!("skipping unknown header: `{header}` = `{value}`");
672 self.unknown.insert(header, value);
673 }
674 }
675
676 Ok(())
677 })()
678 .is_ok()
679 }
680
681 fn finish(self, data: Reader<'data>) -> ParseResult<Headers<'data>> {
682 let not_empty = |s: &&str| !s.is_empty();
683
684 let firmware_revision = self.firmware_revision.ok_or(ParseError::MissingHeader)?;
685 let firmware = Firmware::parse(firmware_revision)?;
686 let internal_firmware = firmware.into();
687
688 let headers = Headers {
690 data,
691
692 main_frame_def: self.main_frames.parse()?,
693 slow_frame_def: self.slow_frames.parse()?,
694 gps_frame_def: self.gps_frames.parse()?,
695 gps_home_frame_def: self.gps_home_frames.parse()?,
696
697 firmware_revision,
698 internal_firmware,
699 firmware,
700 firmware_date: self.firmware_date,
701 board_info: self.board_info.map(str::trim).filter(not_empty),
702 craft_name: self.craft_name.map(str::trim).filter(not_empty),
703
704 debug_mode: self.debug_mode.map_or(Ok(DebugMode::None), |raw| {
705 DebugMode::new(raw.value, internal_firmware)
706 .ok_or_else(|| raw.invalid_header_error())
707 })?,
708 disabled_fields: DisabledFields::new(self.disabled_fields, internal_firmware),
709 features: FeatureSet::new(self.features, internal_firmware),
710 pwm_protocol: self
711 .pwm_protocol
712 .ok_or(ParseError::MissingHeader)
713 .and_then(|raw| {
714 PwmProtocol::new(raw.value, internal_firmware)
715 .ok_or_else(|| raw.invalid_header_error())
716 })?,
717
718 vbat_reference: self.vbat_reference,
719 acceleration_1g: self.acceleration_1g,
720 gyro_scale: self.gyro_scale,
721
722 min_throttle: self.min_throttle,
723 motor_output_range: self.motor_output_range,
724
725 unknown: self.unknown,
726 };
727
728 headers.validate()?;
729
730 Ok(headers)
731 }
732}
733
734fn parse_header<'data>(bytes: &mut Reader<'data>) -> InternalResult<(&'data str, &'data str)> {
736 match bytes.read_u8() {
737 Some(b'H') => {}
738 Some(_) => return Err(InternalError::Retry),
739 None => return Err(InternalError::Eof),
740 }
741
742 let line = bytes.read_line().ok_or(InternalError::Eof)?;
743
744 let line = str::from_utf8(line).map_err(|_| InternalError::Retry)?;
745 let line = line.strip_prefix(' ').unwrap_or(line);
746 let (name, value) = line.split_once(':').ok_or(InternalError::Retry)?;
747
748 tracing::trace!("read header `{name}` = `{value}`");
749
750 Ok((name, value))
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756
757 #[test]
758 #[should_panic(expected = "Retry")]
759 fn invalid_utf8() {
760 let mut b = Reader::new(b"H \xFF:\xFF\n");
761 parse_header(&mut b).unwrap();
762 }
763}