Skip to main content

mavlink_core/
reader.rs

1//! Incremental blocking MAVLink reader.
2
3use crate::{
4    Dialect, MAVLinkMessageRaw, MAVLinkV1MessageRaw, MAVLinkV2MessageRaw, MavHeader,
5    MavlinkVersion, Message, SigningData,
6    error::MessageReadError,
7    frame_decoder::{FrameDecoder, FrameRef, VersionFilter},
8    static_dialect::StaticDialect,
9};
10
11#[cfg(all(feature = "embedded", not(feature = "std")))]
12use embedded_io::Read;
13#[cfg(feature = "std")]
14use std::io::Read;
15
16/// Incrementally reads complete MAVLink frames from a blocking byte stream.
17///
18/// The reader keeps partial and read-ahead data between calls. Use the same
19/// MAVLink dialect `M` for the lifetime of a stream, because CRC validation is
20/// dialect-specific.
21pub struct MavlinkReader<R> {
22    source: R,
23    decoder: FrameDecoder,
24}
25
26impl<R> MavlinkReader<R> {
27    /// Creates a reader around `source` with the default read-ahead capacity.
28    ///
29    /// The buffer is allocated once and reused for the reader's lifetime.
30    #[cfg(feature = "std")]
31    pub fn new(source: R) -> Self
32    where
33        R: Read,
34    {
35        Self {
36            source,
37            decoder: FrameDecoder::new(),
38        }
39    }
40
41    /// Creates an allocation-free reader around `source`.
42    #[cfg(all(feature = "embedded", not(feature = "std")))]
43    pub const fn new(source: R) -> Self
44    where
45        R: Read,
46    {
47        Self {
48            source,
49            decoder: FrameDecoder::new(),
50        }
51    }
52
53    /// Creates a reader with at least `capacity` bytes of read-ahead space.
54    ///
55    /// Capacities smaller than one maximum-size MAVLink frame are raised to
56    /// [`consts::MAX_FRAME_SIZE`](crate::consts::MAX_FRAME_SIZE). The buffer is
57    /// allocated once and reused for the lifetime of the reader.
58    #[cfg(feature = "std")]
59    pub fn with_capacity(capacity: usize, source: R) -> Self
60    where
61        R: Read,
62    {
63        Self {
64            source,
65            decoder: FrameDecoder::with_capacity(capacity),
66        }
67    }
68
69    /// Returns a shared reference to the underlying source.
70    pub const fn get_ref(&self) -> &R {
71        &self.source
72    }
73
74    /// Returns a mutable reference to the underlying source.
75    ///
76    /// Reading directly from the source can skip bytes already buffered by
77    /// this reader and should therefore be avoided.
78    pub const fn get_mut(&mut self) -> &mut R {
79        &mut self.source
80    }
81
82    /// Returns the underlying source.
83    ///
84    /// Any bytes read ahead by this reader are discarded.
85    pub fn into_inner(self) -> R {
86        self.source
87    }
88}
89
90#[cfg(any(feature = "std", all(feature = "embedded", not(feature = "std"))))]
91impl<R: Read> MavlinkReader<R> {
92    /// Reads and parses the next CRC-valid message accepted by `version`.
93    pub fn read_message<M: Message>(
94        &mut self,
95        version: MavlinkVersion,
96    ) -> Result<(MavHeader, M), MessageReadError> {
97        self.read_message_inner(VersionFilter::Exact(version), None)
98    }
99
100    /// Like [`Self::read_message`], using `dialect` for CRC validation and decoding.
101    pub fn read_message_with_dialect<D: Dialect>(
102        &mut self,
103        dialect: &D,
104        version: MavlinkVersion,
105    ) -> Result<(MavHeader, D::Message), MessageReadError> {
106        self.read_message_with_dialect_inner(VersionFilter::Exact(version), dialect, None)
107    }
108
109    /// Reads and parses the next CRC-valid MAVLink 1 or MAVLink 2 message.
110    pub fn read_any_message<M: Message>(&mut self) -> Result<(MavHeader, M), MessageReadError> {
111        self.read_message_inner(VersionFilter::Any, None)
112    }
113
114    /// Like [`Self::read_any_message`], using `dialect` for CRC validation and decoding.
115    pub fn read_any_message_with_dialect<D: Dialect>(
116        &mut self,
117        dialect: &D,
118    ) -> Result<(MavHeader, D::Message), MessageReadError> {
119        self.read_message_with_dialect_inner(VersionFilter::Any, dialect, None)
120    }
121
122    /// Reads the next CRC-valid raw message accepted by `version`.
123    pub fn read_raw_message<M: Message>(
124        &mut self,
125        version: MavlinkVersion,
126    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
127        self.read_raw_message_inner::<M>(VersionFilter::Exact(version), None)
128    }
129
130    /// Like [`Self::read_raw_message`], using `dialect` for CRC validation and decoding.
131    pub fn read_raw_message_with_dialect<D: Dialect>(
132        &mut self,
133        dialect: &D,
134        version: MavlinkVersion,
135    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
136        self.read_raw_message_with_dialect_inner(VersionFilter::Exact(version), dialect, None)
137    }
138
139    /// Reads the next CRC-valid MAVLink 1 or MAVLink 2 raw message.
140    pub fn read_any_raw_message<M: Message>(
141        &mut self,
142    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
143        self.read_raw_message_inner::<M>(VersionFilter::Any, None)
144    }
145
146    /// Like [`Self::read_any_raw_message`], using `dialect` for CRC validation and decoding.
147    pub fn read_any_raw_message_with_dialect<D: Dialect>(
148        &mut self,
149        dialect: &D,
150    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
151        self.read_raw_message_with_dialect_inner(VersionFilter::Any, dialect, None)
152    }
153
154    /// Reads, verifies, and parses the next message accepted by `version`.
155    ///
156    /// With signing data, MAVLink 1 and unsigned MAVLink 2 frames require
157    /// `SigningConfig::allow_unsigned`; signed MAVLink 2 frames must have a valid
158    /// signature. With `None`, signature verification is skipped.
159    #[cfg(feature = "mav2-message-signing")]
160    pub fn read_message_signed<M: Message>(
161        &mut self,
162        version: MavlinkVersion,
163        signing_data: Option<&SigningData>,
164    ) -> Result<(MavHeader, M), MessageReadError> {
165        self.read_message_inner(VersionFilter::Exact(version), signing_data)
166    }
167
168    /// Like [`Self::read_message_signed`], using `dialect` for CRC validation and decoding.
169    #[cfg(feature = "mav2-message-signing")]
170    pub fn read_message_signed_with_dialect<D: Dialect>(
171        &mut self,
172        dialect: &D,
173        version: MavlinkVersion,
174        signing_data: Option<&SigningData>,
175    ) -> Result<(MavHeader, D::Message), MessageReadError> {
176        self.read_message_with_dialect_inner(VersionFilter::Exact(version), dialect, signing_data)
177    }
178
179    /// Reads, verifies, and parses the next MAVLink 1 or MAVLink 2 message.
180    /// With signing data, MAVLink 1 frames follow `SigningConfig::allow_unsigned`.
181    /// With `None`, signature verification is skipped.
182    #[cfg(feature = "mav2-message-signing")]
183    pub fn read_any_message_signed<M: Message>(
184        &mut self,
185        signing_data: Option<&SigningData>,
186    ) -> Result<(MavHeader, M), MessageReadError> {
187        self.read_message_inner(VersionFilter::Any, signing_data)
188    }
189
190    /// Like [`Self::read_any_message_signed`], using `dialect` for CRC validation and decoding.
191    #[cfg(feature = "mav2-message-signing")]
192    pub fn read_any_message_signed_with_dialect<D: Dialect>(
193        &mut self,
194        dialect: &D,
195        signing_data: Option<&SigningData>,
196    ) -> Result<(MavHeader, D::Message), MessageReadError> {
197        self.read_message_with_dialect_inner(VersionFilter::Any, dialect, signing_data)
198    }
199
200    /// Reads and verifies the next raw message accepted by `version`.
201    ///
202    /// With signing data, MAVLink 1 and unsigned MAVLink 2 frames require
203    /// `SigningConfig::allow_unsigned`; signed MAVLink 2 frames must have a valid
204    /// signature. With `None`, signature verification is skipped.
205    #[cfg(feature = "mav2-message-signing")]
206    pub fn read_raw_message_signed<M: Message>(
207        &mut self,
208        version: MavlinkVersion,
209        signing_data: Option<&SigningData>,
210    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
211        self.read_raw_message_inner::<M>(VersionFilter::Exact(version), signing_data)
212    }
213
214    /// Like [`Self::read_raw_message_signed`], using `dialect` for CRC validation and decoding.
215    #[cfg(feature = "mav2-message-signing")]
216    pub fn read_raw_message_signed_with_dialect<D: Dialect>(
217        &mut self,
218        dialect: &D,
219        version: MavlinkVersion,
220        signing_data: Option<&SigningData>,
221    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
222        self.read_raw_message_with_dialect_inner(
223            VersionFilter::Exact(version),
224            dialect,
225            signing_data,
226        )
227    }
228
229    /// Reads and verifies the next MAVLink 1 or MAVLink 2 raw message.
230    /// With signing data, MAVLink 1 frames follow `SigningConfig::allow_unsigned`.
231    /// With `None`, signature verification is skipped.
232    #[cfg(feature = "mav2-message-signing")]
233    pub fn read_any_raw_message_signed<M: Message>(
234        &mut self,
235        signing_data: Option<&SigningData>,
236    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
237        self.read_raw_message_inner::<M>(VersionFilter::Any, signing_data)
238    }
239
240    /// Like [`Self::read_any_raw_message_signed`], using `dialect` for CRC validation and decoding.
241    #[cfg(feature = "mav2-message-signing")]
242    pub fn read_any_raw_message_signed_with_dialect<D: Dialect>(
243        &mut self,
244        dialect: &D,
245        signing_data: Option<&SigningData>,
246    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
247        self.read_raw_message_with_dialect_inner(VersionFilter::Any, dialect, signing_data)
248    }
249
250    #[inline]
251    pub(crate) fn read_message_inner<M: Message>(
252        &mut self,
253        filter: VersionFilter,
254        signing_data: Option<&SigningData>,
255    ) -> Result<(MavHeader, M), MessageReadError> {
256        self.read_message_with_dialect_inner(filter, &StaticDialect::<M>::new(), signing_data)
257    }
258
259    #[inline]
260    pub(crate) fn read_message_with_dialect_inner<D: Dialect>(
261        &mut self,
262        filter: VersionFilter,
263        dialect: &D,
264        signing_data: Option<&SigningData>,
265    ) -> Result<(MavHeader, D::Message), MessageReadError> {
266        loop {
267            if let Some(result) =
268                try_decode_message(&mut self.decoder, filter, dialect, signing_data)
269            {
270                return result;
271            }
272            self.read_more()?;
273        }
274    }
275
276    #[inline]
277    pub(crate) fn read_raw_message_inner<M: Message>(
278        &mut self,
279        filter: VersionFilter,
280        signing_data: Option<&SigningData>,
281    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
282        self.read_raw_message_with_dialect_inner(filter, &StaticDialect::<M>::new(), signing_data)
283    }
284
285    #[inline]
286    pub(crate) fn read_raw_message_with_dialect_inner<D: Dialect>(
287        &mut self,
288        filter: VersionFilter,
289        dialect: &D,
290        signing_data: Option<&SigningData>,
291    ) -> Result<MAVLinkMessageRaw, MessageReadError> {
292        loop {
293            if let Some(message) =
294                try_decode_raw_message(&mut self.decoder, filter, dialect, signing_data)
295            {
296                return Ok(message);
297            }
298            self.read_more()?;
299        }
300    }
301
302    fn read_more(&mut self) -> Result<(), MessageReadError> {
303        let destination = self.decoder.spare_capacity_mut();
304        assert!(
305            !destination.is_empty(),
306            "a pending MAVLink frame must fit in the decoder buffer"
307        );
308
309        #[cfg(feature = "std")]
310        let count = loop {
311            match self.source.read(destination) {
312                Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
313                result => break result?,
314            }
315        };
316
317        #[cfg(all(feature = "embedded", not(feature = "std")))]
318        let count = self
319            .source
320            .read(destination)
321            .map_err(|_| MessageReadError::Io)?;
322
323        if count == 0 {
324            return Err(MessageReadError::eof());
325        }
326        self.decoder.commit(count);
327        Ok(())
328    }
329}
330
331#[inline(always)]
332pub(crate) fn try_decode_message<D: Dialect>(
333    decoder: &mut FrameDecoder,
334    filter: VersionFilter,
335    dialect: &D,
336    signing_data: Option<&SigningData>,
337) -> Option<Result<(MavHeader, D::Message), MessageReadError>> {
338    #[allow(clippy::never_loop)]
339    loop {
340        let meta = decoder.next_frame(filter, dialect)?;
341
342        #[cfg(feature = "mav2-message-signing")]
343        if let Some(signing_data) = signing_data
344            && !signature_is_valid(decoder.frame(meta), signing_data)
345        {
346            decoder.advance(meta);
347            continue;
348        }
349
350        #[cfg(not(feature = "mav2-message-signing"))]
351        let _ = signing_data;
352
353        let (header, parsed) = {
354            let frame = decoder.frame(meta);
355            (
356                frame_header(frame),
357                dialect.decode(frame.version(), frame.message_id(), frame.payload()),
358            )
359        };
360        decoder.advance(meta);
361        return Some(parsed.map(|message| (header, message)).map_err(Into::into));
362    }
363}
364
365#[inline]
366pub(crate) fn try_decode_raw_message<D: Dialect>(
367    decoder: &mut FrameDecoder,
368    filter: VersionFilter,
369    dialect: &D,
370    signing_data: Option<&SigningData>,
371) -> Option<MAVLinkMessageRaw> {
372    #[allow(clippy::never_loop)]
373    loop {
374        let meta = decoder.next_frame(filter, dialect)?;
375        let frame = decoder.frame(meta);
376
377        let message = match frame.version() {
378            MavlinkVersion::V1 => MAVLinkMessageRaw::V1(MAVLinkV1MessageRaw::from(frame)),
379            MavlinkVersion::V2 => MAVLinkMessageRaw::V2(MAVLinkV2MessageRaw::from(frame)),
380        };
381
382        #[cfg(feature = "mav2-message-signing")]
383        if let Some(signing_data) = signing_data
384            && !raw_signature_is_valid(&message, signing_data)
385        {
386            decoder.advance(meta);
387            continue;
388        }
389
390        #[cfg(not(feature = "mav2-message-signing"))]
391        let _ = signing_data;
392
393        decoder.advance(meta);
394        return Some(message);
395    }
396}
397
398#[inline]
399fn frame_header(frame: FrameRef<'_>) -> MavHeader {
400    MavHeader {
401        sequence: frame.sequence(),
402        system_id: frame.system_id(),
403        component_id: frame.component_id(),
404    }
405}
406
407#[cfg(feature = "mav2-message-signing")]
408fn signature_is_valid(frame: FrameRef<'_>, signing_data: &SigningData) -> bool {
409    match frame.version() {
410        MavlinkVersion::V1 => signing_data.config.allow_unsigned,
411        MavlinkVersion::V2 => signing_data.verify_signature(&MAVLinkV2MessageRaw::from(frame)),
412    }
413}
414
415#[cfg(feature = "mav2-message-signing")]
416fn raw_signature_is_valid(message: &MAVLinkMessageRaw, signing_data: &SigningData) -> bool {
417    match message {
418        MAVLinkMessageRaw::V1(_) => signing_data.config.allow_unsigned,
419        MAVLinkMessageRaw::V2(message) => signing_data.verify_signature(message),
420    }
421}
422
423#[cfg(all(test, feature = "std"))]
424mod tests {
425    use super::*;
426    use std::io;
427
428    #[derive(Debug, PartialEq, Eq)]
429    struct TestMessage(u8);
430
431    impl Message for TestMessage {
432        fn message_id(&self) -> u32 {
433            0
434        }
435
436        fn message_name(&self) -> &'static str {
437            "TEST"
438        }
439
440        fn target_system_id(&self) -> Option<u8> {
441            None
442        }
443
444        fn target_component_id(&self) -> Option<u8> {
445            None
446        }
447
448        fn target_system_id_mut(&mut self) -> Option<&mut u8> {
449            None
450        }
451
452        fn target_component_id_mut(&mut self) -> Option<&mut u8> {
453            None
454        }
455
456        fn ser(&self, _: MavlinkVersion, bytes: &mut [u8]) -> usize {
457            bytes[0] = self.0;
458            1
459        }
460
461        fn parse(_: MavlinkVersion, _: u32, payload: &[u8]) -> Result<Self, crate::ParserError> {
462            Ok(Self(payload[0]))
463        }
464
465        fn message_id_from_name(_: &str) -> Option<u32> {
466            None
467        }
468
469        fn default_message_from_id(_: u32) -> Option<Self> {
470            None
471        }
472
473        #[cfg(feature = "arbitrary")]
474        fn random_message_from_id<R: rand::TryRng<Error = core::convert::Infallible>>(
475            _: u32,
476            _: &mut R,
477        ) -> Option<Self> {
478            None
479        }
480
481        fn extra_crc(_: u32) -> u8 {
482            77
483        }
484    }
485
486    struct CountingReader {
487        bytes: std::vec::Vec<u8>,
488        offset: usize,
489        reads: usize,
490    }
491
492    impl Read for CountingReader {
493        fn read(&mut self, destination: &mut [u8]) -> io::Result<usize> {
494            self.reads += 1;
495            let count = destination.len().min(self.bytes.len() - self.offset);
496            destination[..count].copy_from_slice(&self.bytes[self.offset..self.offset + count]);
497            self.offset += count;
498            Ok(count)
499        }
500    }
501
502    #[test]
503    fn standard_reader_capacity_is_configurable_and_frame_safe() {
504        let mut default_reader = MavlinkReader::new(io::empty());
505        assert_eq!(
506            default_reader.decoder.spare_capacity_mut().len(),
507            crate::consts::DEFAULT_READ_BUFFER_CAPACITY
508        );
509
510        let mut minimum_reader = MavlinkReader::with_capacity(1, io::empty());
511        assert_eq!(
512            minimum_reader.decoder.spare_capacity_mut().len(),
513            crate::consts::MAX_FRAME_SIZE
514        );
515    }
516
517    #[test]
518    fn reads_multiple_frames_per_source_read() {
519        let header = MavHeader {
520            sequence: 1,
521            system_id: 2,
522            component_id: 3,
523        };
524        let mut bytes = std::vec::Vec::new();
525        for value in 0..16 {
526            let mut raw = MAVLinkV2MessageRaw::new();
527            raw.serialize_message(header, &TestMessage(value));
528            bytes.extend_from_slice(raw.raw_bytes());
529        }
530
531        let mut reader = MavlinkReader::new(CountingReader {
532            bytes,
533            offset: 0,
534            reads: 0,
535        });
536        for value in 0..16 {
537            let (_, message) = reader.read_any_message::<TestMessage>().unwrap();
538            assert_eq!(message, TestMessage(value));
539        }
540        assert_eq!(reader.get_ref().reads, 1);
541    }
542}