Skip to main content

dtn7_plus/sms/
mod.rs

1use bp7::flags::BlockControlFlags;
2use bp7::*;
3use serde::{Deserialize, Serialize};
4use std::convert::TryFrom;
5use std::time::Duration;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9pub enum SmsError {
10    #[error("message not utf8: {0}")]
11    NonUtf8(#[from] std::string::FromUtf8Error),
12    #[error("serde cbor error: {0}")]
13    Cbor(#[from] serde_cbor::Error),
14    #[error("failed to decompress message: {0}")]
15    SmazDecompress(#[from] smaz::DecompressError),
16    #[error("failed to create endpoint: {0}")]
17    EndpointIdInvalid(#[from] bp7::eid::EndpointIdError),
18    #[error("SMS is missing message text")]
19    NoMessage,
20    #[error("invalid endpoint supplied")]
21    InvalidEndpoint,
22    #[error("payload missing")]
23    PayloadMissing,
24    #[error("invalid sms bundle")]
25    InvalidSmsBundle,
26}
27
28fn smaz_compress(indata: &[u8]) -> Vec<u8> {
29    smaz::compress(indata)
30}
31
32fn smaz_decompress(indata: &[u8]) -> Result<Vec<u8>, SmsError> {
33    Ok(smaz::decompress(indata)?)
34}
35
36#[derive(Debug, PartialEq, Clone)]
37pub struct SMSBundle(Bundle);
38
39impl TryFrom<Bundle> for SMSBundle {
40    type Error = SmsError;
41
42    fn try_from(value: Bundle) -> Result<Self, Self::Error> {
43        let sms_bundle = SMSBundle(value);
44        if sms_bundle.is_valid().is_err() {
45            Err(SmsError::InvalidSmsBundle)
46        } else {
47            Ok(sms_bundle)
48        }
49    }
50}
51
52impl SMSBundle {
53    fn is_eid_valid(&self, eid: &EndpointID) -> Result<(), SmsError> {
54        match eid {
55            EndpointID::Ipn(_, ipn) => {
56                if ipn.service_number() == 767 {
57                    Ok(())
58                } else {
59                    Err(SmsError::InvalidEndpoint)
60                }
61            }
62            EndpointID::Dtn(_, ssp) => {
63                if ssp.service_name() == Some("sms") || ssp.service_name() == Some("~sms") {
64                    Ok(())
65                } else {
66                    Err(SmsError::InvalidEndpoint)
67                }
68            }
69            _ => Err(SmsError::InvalidEndpoint),
70        }
71    }
72    fn is_valid(&self) -> Result<(), SmsError> {
73        self.is_eid_valid(&self.0.primary.source)?;
74        self.is_eid_valid(&self.0.primary.destination)?;
75
76        if self.0.primary.source.is_non_singleton() {
77            return Err(SmsError::InvalidEndpoint);
78        }
79        // Validate general payload
80        let payload = self.0.payload().ok_or(SmsError::PayloadMissing)?;
81        let sms: SMS = serde_cbor::from_slice(payload)?;
82
83        // Validate payload message and compression
84        if sms.comp {
85            String::from_utf8(smaz_decompress(&sms.msg)?)?;
86        } else {
87            String::from_utf8(sms.msg)?;
88        }
89        Ok(())
90    }
91    pub fn id(&self) -> String {
92        self.0.id()
93    }
94    pub fn is_pure(&self, scheme: &str) -> bool {
95        self.0.primary.source.scheme() == scheme && self.0.primary.destination.scheme() == scheme
96    }
97    pub fn src_ipn(&self) -> u64 {
98        match &self.0.primary.source {
99            EndpointID::Ipn(_, addr) => addr.node_number(),
100            _ => 0,
101        }
102    }
103    pub fn dst_ipn(&self) -> u64 {
104        match &self.0.primary.destination {
105            EndpointID::Ipn(_, addr) => addr.node_number(),
106            _ => 0,
107        }
108    }
109    pub fn src(&self) -> Option<String> {
110        self.0.primary.source.node()
111    }
112    pub fn dst(&self) -> Option<String> {
113        self.0.primary.destination.node()
114    }
115    pub fn creation_timestamp(&self) -> &CreationTimestamp {
116        &self.0.primary.creation_timestamp
117    }
118    pub fn sms(&self) -> SMS {
119        let payload = self.0.payload().expect("missing payload in bundle");
120
121        serde_cbor::from_slice(payload).expect("error decoding sms payload")
122    }
123    pub fn compression(&self) -> bool {
124        self.sms().compression()
125    }
126    pub fn encryption(&self) -> bool {
127        self.sms().encryption()
128    }
129    pub fn signature(&self) -> Option<Vec<u8>> {
130        self.sms().signature()
131    }
132    pub fn msg(&self) -> String {
133        self.sms().msg()
134    }
135    pub fn bundle(&self) -> &Bundle {
136        &self.0
137    }
138
139    pub fn to_cbor(&mut self) -> Vec<u8> {
140        self.0.to_cbor()
141    }
142}
143
144#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
145pub struct SMS {
146    comp: bool,
147    enc: bool,
148    #[serde(with = "serde_bytes")]
149    msg: Vec<u8>,
150    sig: Option<Vec<u8>>,
151}
152
153impl SMS {
154    pub fn compression(&self) -> bool {
155        self.comp
156    }
157    pub fn encryption(&self) -> bool {
158        self.enc
159    }
160    pub fn signature(&self) -> Option<Vec<u8>> {
161        self.sig.clone()
162    }
163    pub fn msg(&self) -> String {
164        if self.compression() {
165            String::from_utf8_lossy(&smaz_decompress(&self.msg).expect("decompressing msg failed"))
166                .to_string()
167        } else {
168            String::from_utf8_lossy(&self.msg).to_string()
169        }
170    }
171}
172
173pub struct SmsBuilder {
174    comp: bool,
175    enc: bool,
176    msg: Option<String>,
177    sig: Option<Vec<u8>>,
178}
179
180impl SmsBuilder {
181    pub fn new() -> Self {
182        SmsBuilder {
183            comp: true,
184            enc: false,
185            msg: None,
186            sig: None,
187        }
188    }
189    pub fn compression(mut self, comp: bool) -> Self {
190        self.comp = comp;
191        self
192    }
193    pub fn encryption(mut self, enc: bool) -> Self {
194        self.enc = enc;
195        self
196    }
197    pub fn message(mut self, msg: &str) -> Self {
198        self.msg = Some(msg.into());
199        self
200    }
201    pub fn signature(mut self, sig: Vec<u8>) -> Self {
202        self.sig = Some(sig);
203        self
204    }
205    pub fn build(self) -> Result<SMS, SmsError> {
206        if let Some(msg) = self.msg {
207            let msg_bytes = if self.comp {
208                smaz_compress(msg.as_bytes())
209            } else {
210                msg.as_bytes().to_vec()
211            };
212            Ok(SMS {
213                comp: self.comp,
214                enc: self.enc,
215                msg: msg_bytes,
216                sig: self.sig,
217            })
218        } else {
219            Err(SmsError::NoMessage)
220        }
221    }
222}
223
224impl Default for SmsBuilder {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229/// Create a new sms bundle for IPN addressing scheme
230pub fn new_sms(src: u64, dst: u64, msg: &str, compression: bool) -> Result<SMSBundle, SmsError> {
231    let src_eid = EndpointID::with_ipn(src, 767)?;
232    let dst_eid = EndpointID::with_ipn(dst, 767)?;
233
234    let pblock = primary::PrimaryBlockBuilder::default()
235        .destination(dst_eid)
236        .source(src_eid)
237        .report_to(EndpointID::none())
238        .creation_timestamp(CreationTimestamp::now())
239        .lifetime(Duration::from_secs(60 * 60))
240        .build()
241        .unwrap();
242
243    let payload = SmsBuilder::new()
244        .compression(compression)
245        .message(msg)
246        .build()?;
247    let cblocks = vec![canonical::new_payload_block(
248        BlockControlFlags::empty(),
249        serde_cbor::to_vec(&payload).expect("Fatal failure, could not convert sms payload to CBOR"),
250    )];
251
252    Ok(SMSBundle::try_from(bundle::Bundle::new(pblock, cblocks))
253        .expect("error creating sms bundle"))
254}
255
256#[cfg(test)]
257mod tests {
258    use crate::sms::{new_sms, SMSBundle};
259    use std::convert::TryFrom;
260    #[test]
261    fn test_sms_new_uncompressed() {
262        let mut sms = new_sms(
263            1239468786,
264            1239468999,
265            "The quick brown fox jumps over the lazy dog",
266            false,
267        )
268        .unwrap();
269        let bin_bundle = sms.to_cbor();
270        dbg!(bin_bundle.len());
271        dbg!(bp7::hexify(&bin_bundle));
272    }
273
274    #[test]
275    fn test_sms_new_compressed() {
276        let mut sms = new_sms(
277            1239468786,
278            1239468999,
279            "The quick brown fox jumps over the lazy dog",
280            true,
281        )
282        .unwrap();
283        let bin_bundle = sms.to_cbor();
284        dbg!(bin_bundle.len());
285        dbg!(bp7::hexify(&bin_bundle));
286
287        assert_eq!(
288            dbg!(sms.msg()),
289            "The quick brown fox jumps over the lazy dog"
290        );
291        assert_eq!(dbg!(sms.src().unwrap()), "1239468786"); // leading zeros are stripped
292        assert_eq!(dbg!(sms.dst().unwrap()), "1239468999");
293        dbg!(sms.creation_timestamp());
294    }
295
296    /*#[test]
297    fn test_bundle_compressed() {
298        let mut sms = new_sms(
299            01239468786,
300            01239468999,
301            "The quick brown fox jumps over the lazy dog",
302            true,
303        )
304        .unwrap();
305        let bin_bundle = sms.to_cbor();
306        let compressed_bundle = loragent::compression::snap_compress(&bin_bundle);
307
308        dbg!(bin_bundle.len());
309        dbg!(compressed_bundle.len());
310
311        dbg!(bp7::hexify(&compressed_bundle));
312    }*/
313
314    #[test]
315    fn test_invalid_bundles() {
316        let sms = new_sms(
317            1239468786,
318            1239468999,
319            "The quick brown fox jumps over the lazy dog",
320            true,
321        )
322        .unwrap();
323        let mut raw_bundle = sms.bundle().clone();
324        //let parsed_bundle = SMSBundle::try_from(raw_bundle);
325        assert!(SMSBundle::try_from(raw_bundle.clone()).is_ok());
326
327        raw_bundle.primary.destination = bp7::EndpointID::none();
328        assert!(SMSBundle::try_from(raw_bundle.clone()).is_err());
329
330        raw_bundle.primary.source = bp7::EndpointID::none();
331        assert!(SMSBundle::try_from(raw_bundle.clone()).is_err());
332
333        /*raw_bundle.primary.destination = bp7::EndpointID::with_dtn("1234567/sms").unwrap();
334        assert!(SMSBundle::try_from(raw_bundle.clone()).is_err());
335
336        raw_bundle.primary.source = bp7::EndpointID::with_dtn("node1/sms").unwrap();
337        assert!(SMSBundle::try_from(raw_bundle.clone()).is_err());*/
338
339        raw_bundle.primary.source = bp7::EndpointID::with_ipn(123, 777).unwrap();
340        assert!(SMSBundle::try_from(raw_bundle.clone()).is_err());
341
342        raw_bundle.primary.destination = bp7::EndpointID::with_ipn(123, 777).unwrap();
343        assert!(SMSBundle::try_from(raw_bundle).is_err());
344    }
345
346    #[test]
347    fn test_pureness() {
348        let sms = new_sms(
349            1239468786,
350            1239468999,
351            "The quick brown fox jumps over the lazy dog",
352            true,
353        )
354        .unwrap();
355        let mut raw_bundle = sms.bundle().clone();
356
357        let smsbundle = SMSBundle::try_from(raw_bundle.clone()).unwrap();
358        assert!(smsbundle.is_pure("ipn"));
359
360        raw_bundle.primary.destination = bp7::EndpointID::try_from("dtn://1234567/sms").unwrap();
361        let smsbundle = SMSBundle::try_from(raw_bundle.clone()).unwrap();
362
363        assert!(!smsbundle.is_pure("ipn"));
364
365        raw_bundle.primary.source = bp7::EndpointID::try_from("dtn://1234567/sms").unwrap();
366        let smsbundle = SMSBundle::try_from(raw_bundle).unwrap();
367
368        assert!(smsbundle.is_pure("dtn"));
369    }
370}