orouter_wireless/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//! Defines and implements protocol used on oRouter's physical radio layer (now using LoRa)
3//!
4//! Application level message can be theoretically unlimited, but LoRa can only transmit 255B in
5//! one message. This protocol takes care of splitting message to appropriate number of parts with
6//! necessary added information allowing in to be joined back on receiving end when all parts
7//! arrive.
8//!
9//! [`crate::MessageSlicer`] takes care of the splitting part and is used before the data is
10//! transmitted using oRouter. [`crate::MessagePool`] is used on receiving end to put parts of the
11//! application level / logical message together to form the original message back. Note that the
12//! parts don't have to arrive in order, only that all parts of the message have to arrive
13//! eventually.
14//!
15//! [`crate::WirelessMessagePart`] represents raw chunk of data transmitted/received using oRouters
16//! radio chip. This crate implements and uses following scheme for message part:
17//!
18//! | name          | length in bytes | description                                                                               |
19//! |---------------|-----------------|-------------------------------------------------------------------------------------------|
20//! | network bytes | 2               | network bytes. always 0xAA 0xCC (will be configurable in next release)                      |
21//! | hash          | 6               | hash - first 3B are random, second 3B form a prefix grouping parts of the message to one  |
22//! | part_num      | 1               | part number 1, 2 or 3 (only 3-part messages supported)                                    |
23//! | total_count   | 1               | total count of messages with this prefix                                                  |
24//! | length        | 1               | length of data                                                                            |
25//! | msg type      | 1               | overline message type                                                                     |
26//! | data type     | 1               | byte identifying data type, if previous field is data                                     |
27//! | data          | 1 - 240         | actual data                                                                               |
28//! | CRC16         | 2               | CRC16 of the whole message (header + data)                                                |
29//!
30//! Example of using a [`crate::MessageSlicer`] to split some data for wireless transmission:
31//!
32//! ```rust,no_run
33//! use orouter_wireless::{MessageSlicer, MessageType, network};
34//!
35//! fn main() {
36//!     // VVV in practice provide a good random seed here VVV
37//!     let mut slicer = orouter_wireless::MessageSlicer::new(1234u64, network::DEFAULT);
38//!     let messages = slicer
39//!         .slice(&[0xc0, 0xff, 0xee], MessageType::Data, 0x01).unwrap();
40//!     println!("slices = {:?}", messages);
41//! }
42//! ```
43//!
44//! Example of using a [`crate::MessagePool`] to assemble data back from received message parts:
45//!
46//! ```rust
47//! use orouter_wireless::MessagePool;
48//!
49//! fn main() {
50//!     let mut message_pool = MessagePool::default();
51//!     // this represents a message part received from oRouter
52//!     //
53//!     // in this example, there is 1 part of total 1 forming the whole message, because the data
54//!     // contained in the message are short
55//!     for part in vec![
56//!         vec![
57//!             0xaa, 0xcc, 0x1b, 0xf2, 0x73, 0x86, 0x80, 0xe1, 0x01, 0x01,
58//!             0x05, 0x01, 0x01, 0x41, 0x48, 0x4f, 0x59, 0x21, 0x53, 0xef
59//!         ]
60//!     ] {
61//!         match message_pool.try_insert(part.clone()) {
62//!             Ok(Some(message)) => assert_eq!(message.data(), b"AHOY!"),
63//!             Ok(None) => {}
64//!             Err(_) => {
65//!                 eprintln!(
66//!                     "error while trying to insert message = {:02x?}",
67//!                     part
68//!                 )
69//!             }
70//!         }
71//!     }
72//! }
73//! ```
74use crc16::{State, X_25};
75
76const NETWORK_BYTES_IDX: usize = 0;
77const NETWORK_BYTES_LENGTH: usize = network::DEFAULT.len();
78
79#[cfg_attr(feature = "no_std", allow(dead_code))]
80const HASH_RND_PART_IDX: usize = NETWORK_BYTES_IDX + NETWORK_BYTES_LENGTH; // 0 + 2 = 2
81const HASH_RND_PART_LENGTH: usize = 3;
82
83#[cfg_attr(feature = "no_std", allow(dead_code))]
84const PREFIX_IDX: usize = HASH_RND_PART_IDX + HASH_RND_PART_LENGTH; // 2 + 3 = 5
85const PREFIX_LENGTH: usize = 3;
86
87/// HASH_* describe the full hash as used in [overline store](
88const HASH_IDX: usize = NETWORK_BYTES_IDX + NETWORK_BYTES_LENGTH; // 0 + 2 = 2
89const HASH_LENGTH: usize = HASH_RND_PART_LENGTH + PREFIX_LENGTH;
90
91const PART_NUMBER_IDX: usize = HASH_IDX + HASH_LENGTH; // 2 + 6 = 8
92const PART_NUMBER_LENGTH: usize = 1;
93
94const TOTAL_COUNT_IDX: usize = PART_NUMBER_IDX + PART_NUMBER_LENGTH; // 8 + 1 = 9
95const TOTAL_COUNT_LENGTH: usize = 1;
96
97const LENGTH_IDX: usize = TOTAL_COUNT_IDX + TOTAL_COUNT_LENGTH; // 9 + 1 = 10
98const LENGTH_LENGTH: usize = 1;
99
100pub const MSG_TYPE_IDX: usize = LENGTH_IDX + LENGTH_LENGTH; // 10 + 1 = 11;
101const MSG_TYPE_LENGTH: usize = 1;
102
103#[cfg_attr(feature = "no_std", allow(dead_code))]
104const DATA_TYPE_IDX: usize = MSG_TYPE_IDX + MSG_TYPE_LENGTH; // 11 + 1 = 12;
105const DATA_TYPE_LENGTH: usize = 1;
106
107const HEADER_LENGTH: usize = NETWORK_BYTES_LENGTH
108    + HASH_LENGTH
109    + PART_NUMBER_LENGTH
110    + TOTAL_COUNT_LENGTH
111    + LENGTH_LENGTH
112    + MSG_TYPE_LENGTH
113    + DATA_TYPE_LENGTH; // 13
114
115const CRC_LENGTH: usize = 2;
116
117#[cfg(feature = "std")]
118mod lib_impl;
119
120pub mod network {
121    /// Specifies a wireless network
122    pub type Network = [u8; 2];
123    pub const DEFAULT: Network = [0xAA, 0xCC];
124    pub const TEST: Network = [0xCC, 0xAA];
125}
126
127#[cfg(feature = "std")]
128pub use lib_impl::*;
129
130/// Part of an application level message of arbitrary length, 1-N of these form a whole message
131// this is here because orouter-wireless needs std and cannot be imported as project dependency
132#[cfg(feature = "no_std")]
133pub type WirelessMessagePart = heapless::Vec<u8, MAX_LORA_MESSAGE_SIZE>;
134
135/// Part of an application level message of arbitrary length, 1-N of these for a whole message
136#[cfg(feature = "std")]
137pub type WirelessMessagePart = Vec<u8>;
138
139pub const MAX_LORA_MESSAGE_SIZE: usize = 255;
140
141// FIXME define these according to the design document
142/// Represents different message types which are treated differently by the oRouter hardware
143#[derive(PartialEq, Clone)]
144#[cfg_attr(feature = "std", derive(Debug))]
145#[cfg_attr(feature = "no_std", derive(defmt::Format))]
146pub enum MessageType {
147    /// Common data message, no meaning for orouter
148    Data,
149    /// Request for a distance challenge
150    Challenge,
151    /// Proof of distance
152    Proof,
153    /// TBD
154    Flush,
155    Receipt,
156    /// Any other message type
157    Other,
158}
159
160impl From<u8> for MessageType {
161    fn from(n: u8) -> Self {
162        match n {
163            0x01 => Self::Data,
164            0x02 => Self::Challenge,
165            0x03 => Self::Proof,
166            0x04 => Self::Flush,
167            0x05 => Self::Receipt,
168            _ => Self::Other,
169        }
170    }
171}
172
173impl Into<u8> for MessageType {
174    fn into(self) -> u8 {
175        match self {
176            Self::Data => 0x01,
177            Self::Challenge => 0x02,
178            Self::Proof => 0x03,
179            Self::Flush => 0x04,
180            Self::Receipt => 0x05,
181            Self::Other => 0xff,
182        }
183    }
184}
185
186#[cfg_attr(feature = "std", derive(Debug, PartialEq))]
187#[cfg_attr(feature = "no_std", derive(defmt::Format))]
188pub enum ValidationError {
189    LessThanMinimalLength,
190    NetworkBytesMismatch,
191    PartNumHigherThanTotalCount,
192    IndicatedLenHigherThanMaxLen,
193    IndicatedLenDifferentFromActualLen,
194    // expected, actual
195    IncorrectCrc(u16, u16),
196}
197
198/// validates if slice of bytes follow rules set by the protocol implementing in the crate
199pub fn is_valid_message(network: network::Network, msg: &[u8]) -> Result<(), ValidationError> {
200    // HEADER + 1B data + 2B CRC16
201    if msg.len() < HEADER_LENGTH + 1 + CRC_LENGTH {
202        return Err(ValidationError::LessThanMinimalLength);
203    }
204
205    let network_actual = &msg[NETWORK_BYTES_IDX..NETWORK_BYTES_LENGTH];
206    if network_actual != network {
207        return Err(ValidationError::NetworkBytesMismatch);
208    }
209
210    let len = &msg[LENGTH_IDX];
211    let part_num = &msg[PART_NUMBER_IDX];
212    let total_count = &msg[TOTAL_COUNT_IDX];
213
214    if *part_num > *total_count {
215        return Err(ValidationError::PartNumHigherThanTotalCount);
216    }
217
218    // max data length can be (255-header length-2B CRC)
219    let max_length = crate::MAX_LORA_MESSAGE_SIZE - HEADER_LENGTH - CRC_LENGTH;
220    if *len as usize > max_length {
221        return Err(ValidationError::IndicatedLenHigherThanMaxLen);
222    }
223
224    // claimed len is different from actual remaining data bytes
225    if *len as usize != msg.len() - HEADER_LENGTH - CRC_LENGTH {
226        return Err(ValidationError::IndicatedLenDifferentFromActualLen);
227    }
228
229    // check crc
230    // [0, 1, 2, 3, 4]
231    let i = msg.len() - CRC_LENGTH;
232    let expected_crc = &msg[i..];
233    let data = &msg[..i];
234    let actual_crc = State::<X_25>::calculate(data);
235    if actual_crc.to_be_bytes() != expected_crc {
236        return Err(ValidationError::IncorrectCrc(
237            u16::from_be_bytes([expected_crc[0], expected_crc[1]]),
238            u16::from_be(actual_crc),
239        ));
240    }
241
242    Ok(())
243}
244
245#[cfg(test)]
246mod tests {
247    use super::is_valid_message;
248    use super::ValidationError;
249
250    #[test]
251    fn test_is_valid_message_missing_network_bytes() {
252        //                             hash                             | num | tot | len | data
253        assert_eq!(
254            Err(ValidationError::NetworkBytesMismatch),
255            is_valid_message(
256                crate::network::DEFAULT,
257                &[
258                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
259                    0xff, 0x04, 0x01, 0x02
260                ]
261            )
262        );
263    }
264
265    #[test]
266    fn test_is_valid_message_shorter_than_possible() {
267        //                             prefix               | num | tot | len | data
268        assert_eq!(
269            Err(ValidationError::LessThanMinimalLength),
270            is_valid_message(
271                crate::network::DEFAULT,
272                &[0xff, 0xff, 0xff, 0xff, 0x04, 0x01, 0x02]
273            )
274        );
275    }
276
277    #[test]
278    fn test_is_valid_message_wrong_num() {
279        assert_eq!(
280            Err(ValidationError::PartNumHigherThanTotalCount),
281            is_valid_message(
282                crate::network::DEFAULT,
283                &[
284                    // network | hash                             | num | tot | len | typ  |dtyp| data
285                    0xAA, 0xCC, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x01, 0x02, 0x01, 0x01,
286                    0xff, 0xff, 0x4b, 0x8c // crc16
287                ]
288            )
289        );
290    }
291
292    #[test]
293    fn test_is_valid_message_wrong_len() {
294        assert_eq!(
295            Err(ValidationError::IndicatedLenDifferentFromActualLen),
296            is_valid_message(
297                crate::network::DEFAULT,
298                &[
299                    // network | hash                             | num | tot | len | typ  |dtyp| data
300                    0xAA, 0xCC, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01,
301                    0xff, 0xff, 0xfe, 0x2e // crc16
302                ],
303            )
304        );
305    }
306}