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}