semtech_udp/packet/
mod.rs

1#![allow(clippy::upper_case_acronyms)]
2use num_enum::TryFromPrimitive;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6mod types;
7pub use lora_modulation::{Bandwidth, CodingRate, SpreadingFactor};
8pub use types::{DataRate, Modulation};
9
10mod error;
11pub use error::{Error, ParseError};
12pub type Result<T = ()> = std::result::Result<T, Error>;
13
14pub use macaddr::MacAddr8 as MacAddress;
15
16const PROTOCOL_VERSION: u8 = 2;
17
18#[derive(Debug, Eq, PartialEq, TryFromPrimitive, Clone)]
19#[repr(u8)]
20pub enum Identifier {
21    PushData = 0,
22    PushAck = 1,
23    PullData = 2,
24    PullResp = 3,
25    PullAck = 4,
26    TxAck = 5,
27}
28
29impl fmt::Display for Identifier {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        write!(f, "{self:?}")
32    }
33}
34
35pub mod pull_ack;
36pub mod pull_data;
37pub mod pull_resp;
38pub mod push_ack;
39pub mod push_data;
40pub mod tx_ack;
41
42pub mod parser;
43
44#[derive(Debug, Clone)]
45pub enum Packet {
46    Up(Up),
47    Down(Down),
48}
49
50impl SerializablePacket for Packet {
51    fn serialize(&self, buffer: &mut [u8]) -> Result<u64> {
52        match self {
53            Packet::Up(up) => match up {
54                Up::PushData(pkt) => pkt.serialize(buffer),
55                Up::PullData(pkt) => pkt.serialize(buffer),
56                Up::TxAck(pkt) => pkt.serialize(buffer),
57            },
58            Packet::Down(down) => match down {
59                Down::PushAck(pkt) => pkt.serialize(buffer),
60                Down::PullAck(pkt) => pkt.serialize(buffer),
61                Down::PullResp(pkt) => pkt.serialize(buffer),
62            },
63        }
64    }
65}
66
67#[derive(Debug, Clone)]
68pub enum Up {
69    PushData(push_data::Packet),
70    PullData(pull_data::Packet),
71    TxAck(tx_ack::Packet),
72}
73
74impl Up {
75    pub fn set_gateway_mac(&mut self, mac: MacAddress) {
76        match self {
77            Up::PushData(push_data) => push_data.gateway_mac = mac,
78            Up::PullData(pull_data) => pull_data.gateway_mac = mac,
79            Up::TxAck(tx_ack) => tx_ack.gateway_mac = mac,
80        }
81    }
82}
83
84#[derive(Debug, Clone)]
85pub enum Down {
86    PushAck(push_ack::Packet),
87    PullAck(pull_ack::Packet),
88    PullResp(Box<pull_resp::Packet>),
89}
90
91use std::io::{Cursor, Write};
92
93fn write_preamble(w: &mut Cursor<&mut [u8]>, token: u16) -> Result {
94    Ok(w.write_all(&[PROTOCOL_VERSION, (token >> 8) as u8, token as u8])?)
95}
96
97#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
98#[serde(untagged)]
99pub enum Tmst {
100    Immediate,
101    Tmst(u32),
102}
103use serde::Deserializer;
104
105impl<'de> Deserialize<'de> for Tmst {
106    fn deserialize<D>(deserializer: D) -> std::result::Result<Tmst, D::Error>
107    where
108        D: Deserializer<'de>,
109    {
110        use serde::de::Error;
111        use serde_json::Value;
112        // use the JSON deserialize so as to accept strings or nums
113        let value = Value::deserialize(deserializer)?;
114        if let Value::String(str) = value {
115            if str == "immediate" {
116                Ok(Tmst::Immediate)
117            } else {
118                Err(Error::custom("invalid string for tmst field"))
119            }
120        } else if let Value::Number(num) = value {
121            match num.as_u64() {
122                Some(value) => {
123                    if value < 2_u64.pow(32) {
124                        Ok(Tmst::Tmst(value as u32))
125                    } else {
126                        Err(Error::custom(
127                            "tmst field must be a 32-bit number. it appears to be larger",
128                        ))
129                    }
130                }
131                None => Err(Error::custom(
132                    "when tmst field is a number, it must be an integer",
133                )),
134            }
135        } else {
136            Err(Error::custom("tmst field must be string or number"))
137        }
138    }
139}
140
141pub trait SerializablePacket {
142    fn serialize(&self, buffer: &mut [u8]) -> Result<u64>;
143}
144
145#[macro_export]
146// Up Packets feature Gateway Mac
147macro_rules! simple_up_packet {
148    ($packet:ident,$name:expr) => {
149        impl SerializablePacket for $packet {
150            fn serialize(&self, buffer: &mut [u8]) -> Result<u64> {
151                let mut w = Cursor::new(buffer);
152                write_preamble(&mut w, self.random_token)?;
153                w.write_all(&[$name as u8])?;
154                w.write_all(&self.gateway_mac.as_bytes())?;
155                Ok(w.position())
156            }
157        }
158    };
159}
160
161#[macro_export]
162// Down packets only have random token and identifier
163macro_rules! simple_down_packet {
164    ($packet:ident,$name:expr) => {
165        impl SerializablePacket for $packet {
166            fn serialize(&self, buffer: &mut [u8]) -> std::result::Result<u64, PktError> {
167                let mut w = Cursor::new(buffer);
168                write_preamble(&mut w, self.random_token)?;
169                w.write_all(&[$name as u8])?;
170                Ok(w.position())
171            }
172        }
173    };
174}