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