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 let payload = self.0.payload().ok_or(SmsError::PayloadMissing)?;
81 let sms: SMS = serde_cbor::from_slice(payload)?;
82
83 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}
229pub 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"); assert_eq!(dbg!(sms.dst().unwrap()), "1239468999");
293 dbg!(sms.creation_timestamp());
294 }
295
296 #[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 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.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}