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}