mavlink_core/
signing.rs

1use crate::MAVLinkV2MessageRaw;
2
3use std::time::SystemTime;
4use std::{collections::HashMap, sync::Mutex};
5
6use crate::MAVLINK_IFLAG_SIGNED;
7
8/// Configuration used for MAVLink 2 messages signing as defined in <https://mavlink.io/en/guide/message_signing.html>.
9///
10/// To use a [`SigningConfig`] for sending and reciving messages create a [`SigningData`] object using `SigningData::from_config`.
11///
12/// # Examples
13/// Creating `SigningData`:
14/// ```
15/// # use mavlink_core::{SigningData, SigningConfig};
16/// let config = SigningConfig::new([0u8; 32], 0, true, false);
17/// let sign_data = SigningData::from_config(config);
18/// ```
19///
20#[derive(Debug, Clone)]
21pub struct SigningConfig {
22    secret_key: [u8; 32],
23    link_id: u8,
24    pub(crate) sign_outgoing: bool,
25    pub(crate) allow_unsigned: bool,
26}
27
28// mutable state of signing per connection
29pub(crate) struct SigningState {
30    timestamp: u64,
31    stream_timestamps: HashMap<(u8, u8, u8), u64>,
32}
33
34/// MAVLink 2 message signing data
35///
36/// Contains a [`SigningConfig`] as well as a mutable state that is reused for all messages in a connection.  
37pub struct SigningData {
38    pub(crate) config: SigningConfig,
39    pub(crate) state: Mutex<SigningState>,
40}
41
42impl SigningConfig {
43    /// Creates a new signing configuration.
44    ///
45    /// If `sign_outgoing` is set messages send using this configuration will be signed.
46    /// If `allow_unsigned` is set, when receiving messages, all unsigned messages are accepted, this may also includes MAVLink 1 messages.
47    pub fn new(
48        secret_key: [u8; 32],
49        link_id: u8,
50        sign_outgoing: bool,
51        allow_unsigned: bool,
52    ) -> Self {
53        Self {
54            secret_key,
55            link_id,
56            sign_outgoing,
57            allow_unsigned,
58        }
59    }
60}
61
62impl SigningData {
63    /// Initializes signing data from a given [`SigningConfig`]
64    pub fn from_config(config: SigningConfig) -> Self {
65        Self {
66            config,
67            state: Mutex::new(SigningState {
68                timestamp: 0,
69                stream_timestamps: HashMap::new(),
70            }),
71        }
72    }
73
74    /// Verify the signature of a MAVLink 2 message.
75    ///
76    /// This respects the `allow_unsigned` parameter in [`SigningConfig`].
77    pub fn verify_signature(&self, message: &MAVLinkV2MessageRaw) -> bool {
78        if message.incompatibility_flags() & MAVLINK_IFLAG_SIGNED > 0 {
79            // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
80            // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
81            // is the `SystemTime::now()` call in `get_current_timestamp()`.
82            let mut state = self
83                .state
84                .lock()
85                .expect("Code holding MutexGuard should not panic.");
86            state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
87            let timestamp = message.signature_timestamp();
88            let src_system = message.system_id();
89            let src_component = message.component_id();
90            let stream_key = (message.signature_link_id(), src_system, src_component);
91            match state.stream_timestamps.get(&stream_key) {
92                Some(stream_timestamp) => {
93                    if timestamp <= *stream_timestamp {
94                        // reject old timestamp
95                        return false;
96                    }
97                }
98                None => {
99                    if timestamp + 60 * 1000 * 100 < state.timestamp {
100                        // bad new stream, more then a minute older the the last one
101                        return false;
102                    }
103                }
104            }
105
106            let mut signature_buffer = [0u8; 6];
107            message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
108            let result = signature_buffer == message.signature_value();
109            if result {
110                // if signature is valid update timestamps
111                state.stream_timestamps.insert(stream_key, timestamp);
112                state.timestamp = u64::max(state.timestamp, timestamp);
113            }
114            result
115        } else {
116            self.config.allow_unsigned
117        }
118    }
119
120    /// Sign a MAVLink 2 message if its incompatibility flag is set accordingly.
121    pub fn sign_message(&self, message: &mut MAVLinkV2MessageRaw) {
122        if message.incompatibility_flags() & MAVLINK_IFLAG_SIGNED > 0 {
123            // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
124            // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
125            // is the `SystemTime::now()` call in `get_current_timestamp()`.
126            let mut state = self
127                .state
128                .lock()
129                .expect("Code holding MutexGuard should not panic.");
130            state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
131            let ts_bytes = u64::to_le_bytes(state.timestamp);
132            message
133                .signature_timestamp_bytes_mut()
134                .copy_from_slice(&ts_bytes[0..6]);
135            *message.signature_link_id_mut() = self.config.link_id;
136
137            let mut signature_buffer = [0u8; 6];
138            message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
139
140            message
141                .signature_value_mut()
142                .copy_from_slice(&signature_buffer);
143            state.timestamp += 1;
144        }
145    }
146
147    fn get_current_timestamp() -> u64 {
148        // fallback to 0 if the system time appears to be before epoch
149        let now = SystemTime::now()
150            .duration_since(SystemTime::UNIX_EPOCH)
151            .map(|n| n.as_micros())
152            .unwrap_or(0);
153        // use 1st January 2015 GMT as offset, fallback to 0 if before that date, the used 48 bit of this will overflow in 2104
154        (now.saturating_sub(1420070400u128 * 1000000u128) / 10u128) as u64
155    }
156}