rusmpp_core/pdus/borrowed/
submit_sm.rs

1use rusmpp_macros::Rusmpp;
2
3use crate::{
4    encode::Length,
5    pdus::borrowed::Pdu,
6    tlvs::{
7        TlvTag,
8        borrowed::{MessageSubmissionRequestTlvValue, Tlv},
9    },
10    types::borrowed::{COctetString, EmptyOrFullCOctetString, OctetString},
11    values::{borrowed::*, *},
12};
13
14/// This operation is used by an ESME to submit a short message to the MC for onward
15/// transmission to a specified short message entity (SME).
16#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Rusmpp)]
17#[rusmpp(decode = borrowed, test = skip)]
18#[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))]
19#[cfg_attr(feature = "serde", derive(::serde::Serialize))]
20pub struct SubmitSm<'a, const N: usize> {
21    /// The service_type parameter can be used to
22    /// indicate the SMS Application service
23    /// associated with the message. Specifying the
24    /// service_type allows the ESME to avail of enhanced
25    /// messaging services such as “replace by service_type”
26    /// or to control the teleservice used on the
27    /// air interface.
28    ///
29    /// Set to NULL for default MC settings.
30    pub service_type: ServiceType<'a>,
31    /// Type of Number for source address.
32    pub source_addr_ton: Ton,
33    /// Numbering Plan Indicator for source address.
34    pub source_addr_npi: Npi,
35    /// Address of SME which originated this message.
36    pub source_addr: COctetString<'a, 1, 21>,
37    /// Type of Number for destination.
38    pub dest_addr_ton: Ton,
39    /// Numbering Plan Indicator for destination.
40    pub dest_addr_npi: Npi,
41    /// Destination address of this
42    /// short message For mobile
43    /// terminated messages, this
44    /// is the directory number of
45    /// the recipient MS
46    pub destination_addr: COctetString<'a, 1, 21>,
47    /// Indicates Message Mode
48    /// and Message Type.
49    pub esm_class: EsmClass,
50    /// Protocol Identifier.
51    /// Network specific field.
52    pub protocol_id: u8,
53    /// Designates the priority level of the message.
54    pub priority_flag: PriorityFlag,
55    /// The short message is to be
56    /// scheduled by the MC for delivery.
57    /// Set to NULL for immediate message delivery.
58    pub schedule_delivery_time: EmptyOrFullCOctetString<'a, 17>,
59    /// The validity period of this message.
60    /// Set to NULL to request the MC default validity period.
61    ///
62    /// Note: this is superseded by the qos_time_to_live TLV if
63    /// specified.
64    pub validity_period: EmptyOrFullCOctetString<'a, 17>,
65    /// Indicator to signify if a MC delivery receipt, manual
66    /// ACK, delivery ACK or an intermediate notification is required.
67    pub registered_delivery: RegisteredDelivery,
68    /// Flag indicating if the submitted message should replace an existing message.
69    pub replace_if_present_flag: ReplaceIfPresentFlag,
70    // Defines the encoding scheme of the short message user data.
71    pub data_coding: DataCoding,
72    /// Indicates the short message to send from a list of pre- defined (‘canned’)
73    /// short messages stored on the MC. If not using a MC canned message, set to NULL.
74    pub sm_default_msg_id: u8,
75    /// Length in octets of the short_message user data.
76    sm_length: u8,
77    /// Up to 255 octets of short message user data.
78    ///
79    /// The exact physical limit for short_message size may
80    /// vary according to the underlying network
81    ///
82    /// Note: this field is superceded by the message_payload TLV if
83    /// specified.
84    #[rusmpp(length = sm_length)]
85    short_message: OctetString<'a, 0, 255>,
86    /// Message submission request TLVs ([`MessageSubmissionRequestTlvValue`]).
87    #[rusmpp(length = "unchecked")]
88    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
89    tlvs: heapless::vec::Vec<Tlv<'a>, N>,
90}
91
92impl<'a, const N: usize> SubmitSm<'a, N> {
93    #[allow(clippy::too_many_arguments)]
94    pub fn new(
95        service_type: ServiceType<'a>,
96        source_addr_ton: Ton,
97        source_addr_npi: Npi,
98        source_addr: COctetString<'a, 1, 21>,
99        dest_addr_ton: Ton,
100        dest_addr_npi: Npi,
101        destination_addr: COctetString<'a, 1, 21>,
102        esm_class: EsmClass,
103        protocol_id: u8,
104        priority_flag: PriorityFlag,
105        schedule_delivery_time: EmptyOrFullCOctetString<'a, 17>,
106        validity_period: EmptyOrFullCOctetString<'a, 17>,
107        registered_delivery: RegisteredDelivery,
108        replace_if_present_flag: ReplaceIfPresentFlag,
109        data_coding: DataCoding,
110        sm_default_msg_id: u8,
111        short_message: OctetString<'a, 0, 255>,
112        tlvs: heapless::vec::Vec<impl Into<MessageSubmissionRequestTlvValue<'a>>, N>,
113    ) -> Self {
114        let tlvs = tlvs.into_iter().map(Into::into).map(From::from).collect();
115
116        let sm_length = short_message.length() as u8;
117
118        let mut submit_sm = Self {
119            service_type,
120            source_addr_ton,
121            source_addr_npi,
122            source_addr,
123            dest_addr_ton,
124            dest_addr_npi,
125            destination_addr,
126            esm_class,
127            protocol_id,
128            priority_flag,
129            schedule_delivery_time,
130            validity_period,
131            registered_delivery,
132            replace_if_present_flag,
133            data_coding,
134            sm_default_msg_id,
135            sm_length,
136            short_message,
137            tlvs,
138        };
139
140        submit_sm.clear_short_message_if_message_payload_exists();
141
142        submit_sm
143    }
144
145    pub fn sm_length(&self) -> u8 {
146        self.sm_length
147    }
148
149    pub fn short_message(&'_ self) -> &'_ OctetString<'_, 0, 255> {
150        &self.short_message
151    }
152
153    /// Sets the short message and short message length.
154    /// Updates the short message and short message length accordingly.
155    /// Has no effect if the message payload is set.
156    /// Returns true if the short message and short message length were set.
157    pub fn set_short_message(&mut self, short_message: OctetString<'a, 0, 255>) -> bool {
158        self.short_message = short_message;
159        self.sm_length = self.short_message.length() as u8;
160
161        !self.clear_short_message_if_message_payload_exists()
162    }
163
164    pub fn tlvs(&'_ self) -> &'_ [Tlv<'_>] {
165        &self.tlvs
166    }
167
168    pub fn set_tlvs(
169        &mut self,
170        tlvs: heapless::vec::Vec<impl Into<MessageSubmissionRequestTlvValue<'a>>, N>,
171    ) {
172        self.tlvs = tlvs.into_iter().map(Into::into).map(From::from).collect();
173
174        self.clear_short_message_if_message_payload_exists();
175    }
176
177    pub fn clear_tlvs(&mut self) {
178        self.tlvs.clear();
179    }
180
181    pub fn push_tlv(
182        &mut self,
183        tlv: impl Into<MessageSubmissionRequestTlvValue<'a>>,
184    ) -> Result<(), Tlv<'a>> {
185        self.tlvs.push(Tlv::from(tlv.into()))?;
186
187        self.clear_short_message_if_message_payload_exists();
188
189        Ok(())
190    }
191
192    /// Clears the short message and short message length if the message payload is set.
193    /// Returns true if the short message and short message length were cleared.
194    fn clear_short_message_if_message_payload_exists(&mut self) -> bool {
195        let message_payload_exists = self
196            .tlvs
197            .iter()
198            .any(|value| matches!(value.tag(), TlvTag::MessagePayload));
199
200        if message_payload_exists {
201            self.short_message = OctetString::empty();
202            self.sm_length = 0;
203
204            return true;
205        };
206
207        false
208    }
209
210    pub fn builder() -> SubmitSmBuilder<'a, N> {
211        SubmitSmBuilder::new()
212    }
213}
214
215impl<'a, const N: usize> From<SubmitSm<'a, N>> for Pdu<'a, N> {
216    fn from(value: SubmitSm<'a, N>) -> Self {
217        Self::SubmitSm(value)
218    }
219}
220
221#[derive(Debug, Default)]
222pub struct SubmitSmBuilder<'a, const N: usize> {
223    inner: SubmitSm<'a, N>,
224}
225
226impl<'a, const N: usize> SubmitSmBuilder<'a, N> {
227    pub fn new() -> Self {
228        Self::default()
229    }
230
231    pub fn service_type(mut self, service_type: ServiceType<'a>) -> Self {
232        self.inner.service_type = service_type;
233        self
234    }
235
236    pub fn source_addr_ton(mut self, source_addr_ton: Ton) -> Self {
237        self.inner.source_addr_ton = source_addr_ton;
238        self
239    }
240
241    pub fn source_addr_npi(mut self, source_addr_npi: Npi) -> Self {
242        self.inner.source_addr_npi = source_addr_npi;
243        self
244    }
245
246    pub fn source_addr(mut self, source_addr: COctetString<'a, 1, 21>) -> Self {
247        self.inner.source_addr = source_addr;
248        self
249    }
250
251    pub fn dest_addr_ton(mut self, dest_addr_ton: Ton) -> Self {
252        self.inner.dest_addr_ton = dest_addr_ton;
253        self
254    }
255
256    pub fn dest_addr_npi(mut self, dest_addr_npi: Npi) -> Self {
257        self.inner.dest_addr_npi = dest_addr_npi;
258        self
259    }
260
261    pub fn destination_addr(mut self, destination_addr: COctetString<'a, 1, 21>) -> Self {
262        self.inner.destination_addr = destination_addr;
263        self
264    }
265
266    pub fn esm_class(mut self, esm_class: EsmClass) -> Self {
267        self.inner.esm_class = esm_class;
268        self
269    }
270
271    pub fn protocol_id(mut self, protocol_id: u8) -> Self {
272        self.inner.protocol_id = protocol_id;
273        self
274    }
275
276    pub fn priority_flag(mut self, priority_flag: PriorityFlag) -> Self {
277        self.inner.priority_flag = priority_flag;
278        self
279    }
280
281    pub fn schedule_delivery_time(
282        mut self,
283        schedule_delivery_time: EmptyOrFullCOctetString<'a, 17>,
284    ) -> Self {
285        self.inner.schedule_delivery_time = schedule_delivery_time;
286        self
287    }
288
289    pub fn validity_period(mut self, validity_period: EmptyOrFullCOctetString<'a, 17>) -> Self {
290        self.inner.validity_period = validity_period;
291        self
292    }
293
294    pub fn registered_delivery(mut self, registered_delivery: RegisteredDelivery) -> Self {
295        self.inner.registered_delivery = registered_delivery;
296        self
297    }
298
299    pub fn replace_if_present_flag(
300        mut self,
301        replace_if_present_flag: ReplaceIfPresentFlag,
302    ) -> Self {
303        self.inner.replace_if_present_flag = replace_if_present_flag;
304        self
305    }
306
307    pub fn data_coding(mut self, data_coding: DataCoding) -> Self {
308        self.inner.data_coding = data_coding;
309        self
310    }
311
312    pub fn sm_default_msg_id(mut self, sm_default_msg_id: u8) -> Self {
313        self.inner.sm_default_msg_id = sm_default_msg_id;
314        self
315    }
316
317    pub fn short_message(mut self, short_message: OctetString<'a, 0, 255>) -> Self {
318        self.inner.set_short_message(short_message);
319        self
320    }
321
322    pub fn tlvs(
323        mut self,
324        tlvs: heapless::vec::Vec<impl Into<MessageSubmissionRequestTlvValue<'a>>, N>,
325    ) -> Self {
326        self.inner.set_tlvs(tlvs);
327        self
328    }
329
330    pub fn clear_tlvs(mut self) -> Self {
331        self.inner.clear_tlvs();
332        self
333    }
334
335    pub fn push_tlv(
336        mut self,
337        tlv: impl Into<MessageSubmissionRequestTlvValue<'a>>,
338    ) -> Result<Self, Tlv<'a>> {
339        self.inner.push_tlv(tlv)?;
340        Ok(self)
341    }
342
343    pub fn build(self) -> SubmitSm<'a, N> {
344        self.inner
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use crate::{tests::TestInstance, types::borrowed::AnyOctetString};
351
352    use super::*;
353
354    impl<const N: usize> TestInstance for SubmitSm<'static, N> {
355        fn instances() -> alloc::vec::Vec<Self> {
356            alloc::vec![
357                Self::default(),
358                Self::builder()
359                    .service_type(ServiceType::new(
360                        GenericServiceType::CellularMessaging.into(),
361                    ))
362                    .source_addr_ton(Ton::International)
363                    .source_addr_npi(Npi::Isdn)
364                    .source_addr(COctetString::new(b"Source Address\0").unwrap())
365                    .dest_addr_ton(Ton::International)
366                    .dest_addr_npi(Npi::Isdn)
367                    .destination_addr(COctetString::new(b"Destination Address\0").unwrap())
368                    .esm_class(EsmClass::new(
369                        MessagingMode::StoreAndForward,
370                        MessageType::ShortMessageContainsMCDeliveryReceipt,
371                        Ansi41Specific::ShortMessageContainsDeliveryAcknowledgement,
372                        GsmFeatures::SetUdhiAndReplyPath,
373                    ))
374                    .protocol_id(0)
375                    .priority_flag(PriorityFlag::from(PriorityFlagType::from(Ansi136::Bulk)))
376                    .schedule_delivery_time(
377                        EmptyOrFullCOctetString::new(b"2023-09-01T12:00\0").unwrap(),
378                    )
379                    .validity_period(EmptyOrFullCOctetString::new(b"2023-10-01T12:00\0").unwrap())
380                    .registered_delivery(RegisteredDelivery::request_all())
381                    .replace_if_present_flag(ReplaceIfPresentFlag::Replace)
382                    .data_coding(DataCoding::Ksc5601)
383                    .sm_default_msg_id(69)
384                    .short_message(OctetString::new(b"Short Message").unwrap())
385                    .build(),
386                Self::builder()
387                    .service_type(ServiceType::new(
388                        GenericServiceType::CellularMessaging.into(),
389                    ))
390                    .source_addr_ton(Ton::International)
391                    .source_addr_npi(Npi::Isdn)
392                    .source_addr(COctetString::new(b"Source Address\0").unwrap())
393                    .dest_addr_ton(Ton::International)
394                    .dest_addr_npi(Npi::Isdn)
395                    .destination_addr(COctetString::new(b"Destination Address\0").unwrap())
396                    .esm_class(EsmClass::new(
397                        MessagingMode::Default,
398                        MessageType::ShortMessageContainsIntermediateDeliveryNotification,
399                        Ansi41Specific::ShortMessageContainsUserAcknowledgment,
400                        GsmFeatures::SetUdhiAndReplyPath,
401                    ))
402                    .protocol_id(0)
403                    .priority_flag(PriorityFlag::from(PriorityFlagType::from(
404                        Ansi136::VeryUrgent,
405                    )))
406                    .schedule_delivery_time(
407                        EmptyOrFullCOctetString::new(b"2023-09-01T12:01\0").unwrap(),
408                    )
409                    .validity_period(EmptyOrFullCOctetString::new(b"2023-10-01T12:20\0").unwrap())
410                    .registered_delivery(RegisteredDelivery::request_all())
411                    .replace_if_present_flag(ReplaceIfPresentFlag::DoNotReplace)
412                    .data_coding(DataCoding::Jis)
413                    .sm_default_msg_id(96)
414                    .short_message(OctetString::new(b"Short Message").unwrap())
415                    .tlvs(
416                        [MessageSubmissionRequestTlvValue::MessagePayload(
417                            MessagePayload::new(AnyOctetString::new(b"Message Payload")),
418                        )]
419                        .into()
420                    )
421                    .build(),
422                Self::builder()
423                    .short_message(OctetString::new(b"Short Message").unwrap())
424                    .tlvs(
425                        [
426                            MessageSubmissionRequestTlvValue::MessagePayload(MessagePayload::new(
427                                AnyOctetString::new(b"Message Payload"),
428                            )),
429                            MessageSubmissionRequestTlvValue::UserResponseCode(3),
430                            MessageSubmissionRequestTlvValue::DestBearerType(
431                                BearerType::FlexReFlex
432                            ),
433                            MessageSubmissionRequestTlvValue::SourceSubaddress(Subaddress::new(
434                                SubaddressTag::NsapOdd,
435                                OctetString::new(b"Subaddress :D\0").unwrap(),
436                            )),
437                        ]
438                        .into()
439                    )
440                    .build(),
441            ]
442        }
443    }
444
445    #[test]
446    fn encode_decode() {
447        crate::tests::borrowed::encode_decode_with_length_test_instances::<SubmitSm<'static, 16>>();
448    }
449
450    #[test]
451    fn short_message_length() {
452        let short_message = OctetString::new(b"Short Message").unwrap();
453
454        let submit_sm = SubmitSm::<'static, 16>::builder()
455            .short_message(short_message.clone())
456            .build();
457
458        assert_eq!(submit_sm.short_message(), &short_message);
459        assert_eq!(submit_sm.sm_length(), short_message.length() as u8);
460    }
461
462    #[test]
463    fn short_message_override() {
464        let short_message_1 = OctetString::new(b"Short Message 101").unwrap();
465        let short_message_2 = OctetString::new(b"Short Message 2").unwrap();
466
467        let submit_sm = SubmitSm::<'static, 16>::builder()
468            .short_message(short_message_1)
469            .short_message(short_message_2.clone())
470            .build();
471
472        assert_eq!(submit_sm.short_message(), &short_message_2);
473        assert_eq!(submit_sm.sm_length(), short_message_2.length() as u8);
474    }
475
476    #[test]
477    fn message_payload_suppresses_short_message() {
478        let short_message = OctetString::new(b"Short Message").unwrap();
479        let message_payload = MessagePayload::new(AnyOctetString::new(b"Message Payload"));
480
481        // Using push_tlv
482        let submit_sm = SubmitSm::<'static, 16>::builder()
483            .short_message(short_message.clone())
484            .push_tlv(MessageSubmissionRequestTlvValue::MessagePayload(
485                message_payload.clone(),
486            ))
487            .expect("Failed to push TLV")
488            .build();
489
490        assert_eq!(submit_sm.short_message(), &OctetString::empty());
491        assert_eq!(submit_sm.sm_length(), 0);
492
493        // Using tlvs
494        let submit_sm = SubmitSm::<'static, 16>::builder()
495            .short_message(short_message.clone())
496            .tlvs(
497                [MessageSubmissionRequestTlvValue::MessagePayload(
498                    message_payload.clone(),
499                )]
500                .into(),
501            )
502            .build();
503
504        assert_eq!(submit_sm.short_message(), &OctetString::empty());
505        assert_eq!(submit_sm.sm_length(), 0);
506
507        // Even setting the short message after the message payload should not set the short message
508        // Using push_tlv
509        let submit_sm = SubmitSm::<'static, 16>::builder()
510            .short_message(short_message.clone())
511            .push_tlv(MessageSubmissionRequestTlvValue::MessagePayload(
512                message_payload.clone(),
513            ))
514            .expect("Failed to push TLV")
515            .short_message(short_message.clone())
516            .build();
517
518        assert_eq!(submit_sm.short_message(), &OctetString::empty());
519        assert_eq!(submit_sm.sm_length(), 0);
520
521        // Using tlvs
522        let submit_sm = SubmitSm::<'static, 16>::builder()
523            .short_message(short_message.clone())
524            .tlvs(
525                [MessageSubmissionRequestTlvValue::MessagePayload(
526                    message_payload.clone(),
527                )]
528                .into(),
529            )
530            .short_message(short_message.clone())
531            .build();
532
533        assert_eq!(submit_sm.short_message(), &OctetString::empty());
534        assert_eq!(submit_sm.sm_length(), 0);
535
536        // Removing the message payload and then setting the short message should set the short message
537        let submit_sm = SubmitSm::<'static, 16>::builder()
538            .push_tlv(MessageSubmissionRequestTlvValue::MessagePayload(
539                message_payload.clone(),
540            ))
541            .expect("Failed to push TLV")
542            .clear_tlvs()
543            .short_message(short_message.clone())
544            .build();
545
546        assert_eq!(submit_sm.short_message(), &short_message);
547        assert_eq!(submit_sm.sm_length(), short_message.length() as u8);
548    }
549}