mavio/protocol/marker/
version.rs

1use core::fmt::Debug;
2
3use crate::consts::{STX_V1, STX_V2};
4use crate::protocol::MavLinkVersion;
5use crate::utils::sealed::Sealed;
6
7#[cfg(doc)]
8use crate::protocol::Frame;
9
10/// <sup>🔒</sup>
11/// Marks structures which may or may not have a specified MAVLink protocol version.
12///
13/// âš  This trait is sealed âš 
14///
15/// For all such structures it is possible to call [`MaybeVersioned::expect`] and
16/// [`MaybeVersioned::matches`] to compare MAVLink version. The blanket implementation of
17/// [`MaybeVersioned`] assumes that everything is compatible by
18/// [vacuous truth](https://en.wikipedia.org/wiki/Vacuous_truth).
19pub trait MaybeVersioned: IsMagicByte + Clone + Debug + Sync + Send + Sealed + 'static {
20    /// Validates that provided frame matches MAVLink protocol version.
21    ///
22    /// The blanket implementation will always return [`Ok`] meaning that everything is compatible.
23    #[inline]
24    fn expect(#[allow(unused_variables)] version: MavLinkVersion) -> Result<(), VersionError> {
25        Ok(())
26    }
27
28    /// Checks that provided version of MAVLink protocol is compatible.
29    ///
30    /// The blanket implementation will always return `true` meaning that everything is compatible.
31    #[inline]
32    fn matches(#[allow(unused_variables)] version: MavLinkVersion) -> bool {
33        true
34    }
35}
36
37/// Marker for entities which are not constrained by a specific MAVLink protocol version.
38///
39/// In the context of [`Frame`](Frame) and [`Header`](crate::protocol::Header) this means
40/// that although these entities are always belong to some MAVLink protocol version, this
41/// information is opaque to the caller. For example, default [`Receiver`](crate::Receiver) will
42/// look up for both `MAVLink 1` and `MAVLink 2` packets and return
43/// [`Frame<Versionless>`](Frame<Versionless>) which then can be converted to their
44/// version-specific form by [`Frame::try_versioned`](Frame::try_into_versioned).
45#[derive(Clone, Copy, Debug, Default)]
46#[cfg_attr(feature = "specta", derive(specta::Type))]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct Versionless;
49impl Sealed for Versionless {}
50impl IsMagicByte for Versionless {}
51
52#[cfg(feature = "specta")]
53impl Sealed for () {}
54#[cfg(feature = "specta")]
55impl IsMagicByte for () {}
56#[cfg(feature = "specta")]
57impl MaybeVersioned for () {}
58
59impl MaybeVersioned for Versionless {}
60
61/// <sup>🔒</sup>
62/// Marks entities which have a specified MAVLink protocol version.
63///
64/// âš  This trait is sealed âš 
65///
66/// Such entities allow to discover their protocol version by [`Versioned::version`] and
67/// provide a static `marker` for themselves. This trait also enables converting [`V1`] / [`V2`]
68/// from type parameter to a type by [`Versioned::v`] and to treat them as unit type instances using
69/// [`Versioned::ver`].
70///
71/// For example, [`Receiver::versioned`](crate::Receiver::versioned) constructs a protocol-specific
72/// receiver which looks up for frames only of a specific dialect.
73///
74/// # Examples
75///
76/// ```rust
77/// use mavio::prelude::*;
78///
79/// fn release_turbofish<V: Versioned>() {
80///     pass_argument(V::v());
81///     stay_with_enum(V::v().ver());
82/// }
83///
84/// fn pass_argument<V: Versioned>(version: V) {
85///     stay_with_enum(version.ver());
86/// }
87///
88/// fn stay_with_enum(version: MavLinkVersion) {
89///     match version {
90///         MavLinkVersion::V1 => { /* MAVLink1 specific */ }
91///         MavLinkVersion::V2 => { /* MAVLink2 specific */ }
92///     }
93/// }
94///
95/// release_turbofish::<V2>();
96/// pass_argument(V1);
97/// stay_with_enum(MavLinkVersion::V2);
98/// ```
99pub trait Versioned: MaybeVersioned {
100    /// MAVLink protocol version of a type.
101    ///
102    /// # Examples
103    ///
104    /// ```rust
105    /// use mavio::prelude::*;
106    ///
107    /// fn feed_with_enum(version: MavLinkVersion) {
108    ///     match version {
109    ///         MavLinkVersion::V1 => { /* MAVLink1 specific */ }
110    ///         MavLinkVersion::V2 => { /* MAVLink2 specific */ }
111    ///     }
112    /// }
113    ///
114    /// feed_with_enum(V1::version());
115    /// ```
116    fn version() -> MavLinkVersion;
117
118    /// MAVLink protocol version of a unit.
119    ///
120    /// Allows to obtain [`MavLinkVersion`] from [`V1`] / [`V2`] as unit types.
121    ///
122    /// # Examples
123    ///
124    /// ```rust
125    /// use mavio::prelude::*;
126    ///
127    /// fn feed_with_argument<V: Versioned>(version: V) {
128    /// #   return;
129    ///     feed_with_enum(version.ver());
130    /// }
131    ///
132    /// fn feed_with_enum(version: MavLinkVersion) {
133    ///     match version {
134    ///         MavLinkVersion::V1 => { /* MAVLink1 specific */ }
135    ///         MavLinkVersion::V2 => { /* MAVLink1 specific */ }
136    ///     }
137    /// }
138    ///
139    /// feed_with_argument(V1);
140    /// ```
141    fn ver(&self) -> MavLinkVersion;
142
143    /// Returns MAVLink version as [`V1`] / [`V2`] for type parameters.
144    ///
145    /// Useful when [`Versioned`] is provided as a type parameter, but you need a corresponding
146    /// marker. This allows to switch between regular arguments and [turbofish](https://turbo.fish/)
147    /// syntax.
148    ///
149    /// # Examples
150    ///
151    /// ```rust
152    /// use mavio::prelude::*;
153    ///
154    /// fn gimme_argument<V: Versioned>(version: V) {
155    /// #   return;
156    ///     gimme_turbofish::<V>();
157    /// }
158    ///
159    /// fn gimme_turbofish<V: Versioned>() {
160    /// #   return;
161    ///     // Hard way
162    ///     match V::version() {
163    ///         MavLinkVersion::V1 => gimme_argument(V1),
164    ///         MavLinkVersion::V2 => gimme_argument(V2),
165    ///     }
166    ///     // Easy way
167    ///     gimme_argument(V::v());
168    /// }
169    ///
170    /// gimme_argument(V2);
171    /// gimme_turbofish::<V2>()
172    /// ```
173    fn v() -> Self;
174}
175
176/// Marks entities which are strictly `MAVLink 1` protocol compliant.
177#[derive(Clone, Copy, Debug, Default)]
178#[cfg_attr(feature = "specta", derive(specta::Type))]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180pub struct V1;
181impl Sealed for V1 {}
182impl IsMagicByte for V1 {
183    #[inline(always)]
184    fn is_magic_byte(byte: u8) -> bool {
185        byte == STX_V1
186    }
187}
188impl MaybeVersioned for V1 {
189    #[inline]
190    fn expect(version: MavLinkVersion) -> Result<(), VersionError> {
191        match_error(MavLinkVersion::V1, version)
192    }
193    #[inline(always)]
194    fn matches(version: MavLinkVersion) -> bool {
195        version == MavLinkVersion::V1
196    }
197}
198impl Versioned for V1 {
199    #[inline(always)]
200    fn version() -> MavLinkVersion {
201        MavLinkVersion::V1
202    }
203
204    fn ver(&self) -> MavLinkVersion {
205        MavLinkVersion::V1
206    }
207
208    #[inline(always)]
209    fn v() -> Self {
210        V1
211    }
212}
213
214/// Marks entities which are strictly `MAVLink 2` protocol compliant.
215#[derive(Clone, Copy, Debug, Default)]
216#[cfg_attr(feature = "specta", derive(specta::Type))]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218pub struct V2;
219impl Sealed for V2 {}
220impl IsMagicByte for V2 {
221    #[inline(always)]
222    fn is_magic_byte(byte: u8) -> bool {
223        byte == STX_V2
224    }
225}
226impl MaybeVersioned for V2 {
227    #[inline]
228    fn expect(version: MavLinkVersion) -> Result<(), VersionError> {
229        match_error(MavLinkVersion::V2, version)
230    }
231
232    #[inline(always)]
233    fn matches(version: MavLinkVersion) -> bool {
234        version == MavLinkVersion::V2
235    }
236}
237impl Versioned for V2 {
238    #[inline(always)]
239    fn version() -> MavLinkVersion {
240        MavLinkVersion::V2
241    }
242
243    #[inline(always)]
244    fn ver(&self) -> MavLinkVersion {
245        MavLinkVersion::V2
246    }
247
248    #[inline(always)]
249    fn v() -> Self {
250        V2
251    }
252}
253
254#[inline]
255fn match_error(expected: MavLinkVersion, actual: MavLinkVersion) -> Result<(), VersionError> {
256    if expected != actual {
257        return Err(VersionError { expected, actual });
258    }
259    Ok(())
260}
261
262mod is_magic_byte {
263    use crate::protocol::MavSTX;
264
265    pub trait IsMagicByte {
266        #[inline]
267        fn is_magic_byte(byte: u8) -> bool {
268            MavSTX::is_magic_byte(byte)
269        }
270    }
271}
272use crate::error::VersionError;
273pub(crate) use is_magic_byte::IsMagicByte;
274
275#[cfg(test)]
276mod version_marker_tests {
277    use super::*;
278
279    #[test]
280    fn version_matching() {
281        V1::expect(MavLinkVersion::V1).unwrap();
282        V2::expect(MavLinkVersion::V2).unwrap();
283
284        Versionless::expect(MavLinkVersion::V1).unwrap();
285        Versionless::expect(MavLinkVersion::V2).unwrap();
286        assert!(Versionless::matches(MavLinkVersion::V1));
287        assert!(Versionless::matches(MavLinkVersion::V2));
288
289        assert!(V1::matches(MavLinkVersion::V1));
290        assert!(V2::matches(MavLinkVersion::V2));
291        assert!(!V1::matches(MavLinkVersion::V2));
292        assert!(!V2::matches(MavLinkVersion::V1));
293
294        fn expect_versioned<V: Versioned>(
295            _: V,
296            version: MavLinkVersion,
297        ) -> Result<(), VersionError> {
298            V::expect(version)
299        }
300
301        expect_versioned(V1, MavLinkVersion::V1).unwrap();
302        expect_versioned(V2, MavLinkVersion::V2).unwrap();
303        assert!(expect_versioned(V1, MavLinkVersion::V2).is_err());
304        assert!(expect_versioned(V2, MavLinkVersion::V1).is_err());
305    }
306}