Skip to main content

smpp_codec/pdus/submission_pdus/
submit_sm_request.rs

1use crate::common::{read_c_string, write_c_string, Npi, PduError, Ton, CMD_SUBMIT_SM, HEADER_LEN};
2use crate::encoding::MessageBody;
3use crate::tlv::{tags, Tlv};
4use std::io::{Cursor, Read, Write};
5
6#[derive(Debug, Clone, PartialEq)]
7/// Request to submit a short message (SubmitSm)
8pub struct SubmitSmRequest {
9    /// Sequence number of the PDU
10    pub sequence_number: u32,
11    /// Service Type (e.g., "CMT", "CPT")
12    pub service_type: String, // Max 6 chars
13    /// Source Address Type of Number
14    pub source_addr_ton: Ton,
15    /// Source Address Numbering Plan Indicator
16    pub source_addr_npi: Npi,
17    /// Source Address
18    pub source_addr: String, // Max 21 chars
19    /// Destination Address Type of Number
20    pub dest_addr_ton: Ton,
21    /// Destination Address Numbering Plan Indicator
22    pub dest_addr_npi: Npi,
23    /// Destination Address
24    pub dest_addr: String, // Max 21 chars
25    /// ESM Class (Message Mode, Message Type, GSM Features)
26    pub esm_class: u8,
27    /// Protocol Identifier
28    pub protocol_id: u8,
29    /// Priority Level
30    pub priority_flag: u8,
31    /// Scheduled Delivery Time (YYMMDDhhmmsstn00)
32    pub schedule_delivery_time: String, // Max 17 chars
33    /// Validity Period (YYMMDDhhmmsstn00)
34    pub validity_period: String, // Max 17 chars
35    /// Registered Delivery (Delivery Receipt Request)
36    pub registered_delivery: u8,
37    /// Replace If Present Flag
38    pub replace_if_present_flag: u8,
39    /// Data Coding Scheme (DCS)
40    pub data_coding: u8,
41    /// SMSC Default Message ID
42    pub sm_default_msg_id: u8,
43    /// Short Message Data
44    pub short_message: Vec<u8>, // Max 254 octets
45    /// Optional Parameters (TLVs)
46    pub optional_params: Vec<Tlv>,
47}
48
49#[derive(Debug, Clone, PartialEq)]
50/// Information about message segmentation/concatenation
51pub struct SegmentationInfo {
52    /// Reference Number (to group segments)
53    pub ref_num: u16,
54    /// Total number of segments
55    pub total_segments: u8,
56    /// Sequence number of this segment (1-based)
57    pub seq_num: u8,
58}
59
60impl SubmitSmRequest {
61    /// Create a new SubmitSm Request with mandatory fields.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use smpp_codec::pdus::SubmitSmRequest;
67    ///
68    /// let sequence_number: u32 = 1;
69    /// let submit = SubmitSmRequest::new(
70    ///     sequence_number,
71    ///     "source".to_string(),
72    ///     "dest".to_string(),
73    ///     b"Hello".to_vec()
74    /// );
75    /// ```
76    pub fn new(
77        sequence_number: u32,
78        source_addr: String,
79        dest_addr: String,
80        short_message: Vec<u8>,
81    ) -> Self {
82        Self {
83            sequence_number,
84            service_type: String::new(),
85            source_addr_ton: Ton::Unknown,
86            source_addr_npi: Npi::Unknown,
87            source_addr,
88            dest_addr_ton: Ton::Unknown,
89            dest_addr_npi: Npi::Unknown,
90            dest_addr,
91            esm_class: 0,
92            protocol_id: 0,
93            priority_flag: 0,
94            schedule_delivery_time: String::new(),
95            validity_period: String::new(),
96            registered_delivery: 0, // Default: Don't request delivery receipt
97            replace_if_present_flag: 0,
98            data_coding: 0, // Default: SMSC Default
99            sm_default_msg_id: 0,
100            short_message,
101            optional_params: Vec::new(),
102        }
103    }
104
105    /// Helper to add a TLV (Optional Parameter)
106    pub fn add_tlv(&mut self, tlv: Tlv) {
107        self.optional_params.push(tlv);
108    }
109
110    /// Encode the struct into raw bytes for the network.
111    ///
112    /// # Errors
113    ///
114    /// Returns a [`PduError`] if:
115    /// * `service_type` > 6 chars
116    /// * `source_addr` or `dest_addr` > 21 chars
117    /// * `schedule_delivery_time` or `validity_period` > 17 chars
118    /// * `short_message` > 254 octets (use `message_payload` TLV for longer messages)
119    /// * Usage of invalid characters when validating C-Strings
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// # use smpp_codec::pdus::SubmitSmRequest;
125    /// # let sequence_number: u32 = 1;
126    /// # let submit = SubmitSmRequest::new(sequence_number, "src".into(), "dst".into(), b"Hi".to_vec());
127    /// let mut buffer = Vec::new();
128    /// submit.encode(&mut buffer).expect("Encoding failed");
129    /// ```
130    pub fn encode(&self, writer: &mut impl Write) -> Result<(), PduError> {
131        // 1. Validation
132        if self.service_type.len() > 6 {
133            return Err(PduError::StringTooLong("service_type".into(), 6));
134        }
135        if self.source_addr.len() > 21 {
136            return Err(PduError::StringTooLong("source_addr".into(), 21));
137        }
138        if self.dest_addr.len() > 21 {
139            return Err(PduError::StringTooLong("dest_addr".into(), 21));
140        }
141        if self.schedule_delivery_time.len() > 17 {
142            return Err(PduError::StringTooLong("schedule_delivery_time".into(), 17));
143        }
144        if self.validity_period.len() > 17 {
145            return Err(PduError::StringTooLong("validity_period".into(), 17));
146        }
147        if self.short_message.len() > 254 {
148            return Err(PduError::InvalidLength);
149        } // Use Payload TLV for longer msgs
150
151        // 2. Calculate Length Upfront
152        let tlvs_len: usize = self
153            .optional_params
154            .iter()
155            .map(|tlv| 4 + tlv.length as usize)
156            .sum();
157
158        // Fixed fields overhead:
159        // Src(Ton1+Npi1) + Dst(Ton1+Npi1) + Flags(3) + Reg/Rep/DC/Id(4) + SmLen(1) = 12 bytes
160        let body_len = self.service_type.len()
161            + 1
162            + (self.source_addr.len() + 1)
163            + (self.dest_addr.len() + 1)
164            + (self.schedule_delivery_time.len() + 1)
165            + (self.validity_period.len() + 1)
166            + self.short_message.len()
167            + 12
168            + tlvs_len;
169
170        // 3. Write Header
171        let command_len = (HEADER_LEN + body_len) as u32;
172        writer.write_all(&command_len.to_be_bytes())?;
173        writer.write_all(&CMD_SUBMIT_SM.to_be_bytes())?;
174        writer.write_all(&0u32.to_be_bytes())?; // Status always 0
175        writer.write_all(&self.sequence_number.to_be_bytes())?;
176
177        // 4. Write Body
178        write_c_string(writer, &self.service_type)?;
179
180        // Source Address
181        writer.write_all(&[self.source_addr_ton as u8, self.source_addr_npi as u8])?;
182        write_c_string(writer, &self.source_addr)?;
183
184        // Destination Address
185        writer.write_all(&[self.dest_addr_ton as u8, self.dest_addr_npi as u8])?;
186        write_c_string(writer, &self.dest_addr)?;
187
188        // Flags & settings
189        writer.write_all(&[self.esm_class, self.protocol_id, self.priority_flag])?;
190
191        write_c_string(writer, &self.schedule_delivery_time)?;
192        write_c_string(writer, &self.validity_period)?;
193
194        writer.write_all(&[
195            self.registered_delivery,
196            self.replace_if_present_flag,
197            self.data_coding,
198            self.sm_default_msg_id,
199        ])?;
200
201        // Short Message (Length + Content)
202        writer.write_all(&[self.short_message.len() as u8])?;
203        writer.write_all(&self.short_message)?;
204
205        // Optional Parameters (TLVs)
206        for tlv in &self.optional_params {
207            tlv.encode(writer)?;
208        }
209
210        Ok(())
211    }
212
213    /// Decode raw bytes from the network into the struct.
214    ///
215    /// # Errors
216    ///
217    /// Returns a [`PduError`] if the buffer is too short or malformed.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// # use smpp_codec::pdus::SubmitSmRequest;
223    /// # let sequence_number: u32 = 1;
224    /// # let submit = SubmitSmRequest::new(sequence_number, "src".into(), "dst".into(), b"Hi".to_vec());
225    /// # let mut buffer = Vec::new();
226    /// # submit.encode(&mut buffer).unwrap();
227    /// let decoded = SubmitSmRequest::decode(&buffer).expect("Decoding failed");
228    /// assert_eq!(decoded.short_message, b"Hi");
229    /// ```
230    pub fn decode(buffer: &[u8]) -> Result<Self, PduError> {
231        if buffer.len() < HEADER_LEN {
232            return Err(PduError::BufferTooShort);
233        }
234
235        let mut cursor = Cursor::new(buffer);
236        cursor.set_position(12); // Skip header (len, id, status)
237        let mut bytes = [0u8; 4];
238        cursor.read_exact(&mut bytes)?;
239        let sequence_number = u32::from_be_bytes(bytes);
240
241        // Body Parsing
242        let mut u8_buf = [0u8; 1];
243
244        let service_type = read_c_string(&mut cursor)?;
245
246        // Source
247        cursor.read_exact(&mut u8_buf)?;
248        let source_addr_ton = Ton::from(u8_buf[0]);
249        cursor.read_exact(&mut u8_buf)?;
250        let source_addr_npi = Npi::from(u8_buf[0]);
251        let source_addr = read_c_string(&mut cursor)?;
252
253        // Dest
254        cursor.read_exact(&mut u8_buf)?;
255        let dest_addr_ton = Ton::from(u8_buf[0]);
256        cursor.read_exact(&mut u8_buf)?;
257        let dest_addr_npi = Npi::from(u8_buf[0]);
258        let dest_addr = read_c_string(&mut cursor)?;
259
260        // Flags
261        cursor.read_exact(&mut u8_buf)?;
262        let esm_class = u8_buf[0];
263        cursor.read_exact(&mut u8_buf)?;
264        let protocol_id = u8_buf[0];
265        cursor.read_exact(&mut u8_buf)?;
266        let priority_flag = u8_buf[0];
267
268        let schedule_delivery_time = read_c_string(&mut cursor)?;
269        let validity_period = read_c_string(&mut cursor)?;
270
271        cursor.read_exact(&mut u8_buf)?;
272        let registered_delivery = u8_buf[0];
273        cursor.read_exact(&mut u8_buf)?;
274        let replace_if_present_flag = u8_buf[0];
275        cursor.read_exact(&mut u8_buf)?;
276        let data_coding = u8_buf[0];
277        cursor.read_exact(&mut u8_buf)?;
278        let sm_default_msg_id = u8_buf[0];
279
280        // Short Message
281        cursor.read_exact(&mut u8_buf)?;
282        let sm_length = u8_buf[0] as usize;
283        let mut short_message = vec![0u8; sm_length];
284        cursor.read_exact(&mut short_message)?;
285
286        // Optional Params (TLVs)
287        let mut optional_params = Vec::new();
288        while let Some(tlv) = Tlv::decode(&mut cursor)? {
289            optional_params.push(tlv);
290        }
291
292        Ok(Self {
293            sequence_number,
294            service_type,
295            source_addr_ton,
296            source_addr_npi,
297            source_addr,
298            dest_addr_ton,
299            dest_addr_npi,
300            dest_addr,
301            esm_class,
302            protocol_id,
303            priority_flag,
304            schedule_delivery_time,
305            validity_period,
306            registered_delivery,
307            replace_if_present_flag,
308            data_coding,
309            sm_default_msg_id,
310            short_message,
311            optional_params,
312        })
313    }
314
315    /// Retrieve segmentation information if present (via SAR headers or UDH).
316    pub fn get_segmentation_info(&self) -> Option<SegmentationInfo> {
317        // STRATEGY 1: Check SAR TLVs (Simpler)
318        // We need all three SAR tags to be present.
319        let sar_ref = self.get_tlv_u16(tags::SAR_MSG_REF_NUM);
320        let sar_total = self.get_tlv_u16(tags::SAR_TOTAL_SEGMENTS);
321        let sar_seq = self.get_tlv_u16(tags::SAR_SEGMENT_SEQNUM);
322
323        if let (Some(ref_num), Some(total), Some(seq)) = (sar_ref, sar_total, sar_seq) {
324            return Some(SegmentationInfo {
325                ref_num,
326                total_segments: total as u8, // SAR uses u16 storage but value is u8
327                seq_num: seq as u8,
328            });
329        }
330
331        // STRATEGY 2: Check UDH (User Data Header)
332        // Only look if the ESM Class "UDHI" bit (0x40) is set.
333        if (self.esm_class & 0x40) != 0 && self.short_message.len() > 5 {
334            let udh_len = self.short_message[0] as usize;
335
336            // Safety check: header must be fully contained in message
337            if self.short_message.len() > udh_len {
338                let header_bytes = &self.short_message[1..=udh_len];
339
340                // Parse UDH Information Elements (IEs)
341                let mut cursor = 0;
342                while cursor < header_bytes.len() {
343                    let ie_id = header_bytes[cursor];
344                    let ie_len = header_bytes[cursor + 1] as usize;
345                    let ie_data = &header_bytes[cursor + 2..cursor + 2 + ie_len];
346
347                    // 0x00: Concatenated short messages, 8-bit reference number
348                    if ie_id == 0x00 && ie_len == 3 {
349                        return Some(SegmentationInfo {
350                            ref_num: ie_data[0] as u16,
351                            total_segments: ie_data[1],
352                            seq_num: ie_data[2],
353                        });
354                    }
355                    // 0x08: Concatenated short messages, 16-bit reference number
356                    else if ie_id == 0x08 && ie_len == 4 {
357                        let ref_num = u16::from_be_bytes([ie_data[0], ie_data[1]]);
358                        return Some(SegmentationInfo {
359                            ref_num,
360                            total_segments: ie_data[2],
361                            seq_num: ie_data[3],
362                        });
363                    }
364
365                    cursor += 2 + ie_len;
366                }
367            }
368        }
369
370        // No segmentation found (Single Message)
371        None
372    }
373
374    /// Parse the message body based on the `data_coding` and `esm_class` (UDHI).
375    pub fn parse_message(&self) -> MessageBody {
376        let has_udh = (self.esm_class & 0x40) != 0;
377        crate::encoding::process_body(&self.short_message, self.data_coding, has_udh)
378    }
379
380    /// Helper to find a TLV and return it as u16 (handles both u8 and u16 storage)
381    fn get_tlv_u16(&self, tag: u16) -> Option<u16> {
382        self.optional_params
383            .iter()
384            .find(|t| t.tag == tag)
385            .and_then(|t| {
386                if t.length == 1 {
387                    Some(t.value[0] as u16)
388                } else if t.length == 2 {
389                    Some(u16::from_be_bytes([t.value[0], t.value[1]]))
390                } else {
391                    None
392                }
393            })
394    }
395}