Skip to main content

smpp_codec/
tlv.rs

1//! # Tagged Length Value (TLV) Parameters
2//!
3//! This module handles the encoding and decoding of SMPP Optional Parameters (TLVs).
4//! It provides a registry of standard tags and mechanisms to parse and serialize them.
5
6use crate::common::PduError;
7use std::io::{Cursor, Read, Write};
8
9// --- Standard SMPP Optional Parameter Tags ---
10/// Standard SMPP Optional Parameter Tags as defined in SMPP 3.4 Spec.
11pub mod tags {
12    pub const DEST_ADDR_SUBUNIT: u16 = 0x0005;
13    pub const DEST_NETWORK_TYPE: u16 = 0x0006;
14    pub const DEST_BEARER_TYPE: u16 = 0x0007;
15    pub const DEST_TELEMATICS_ID: u16 = 0x0008;
16    pub const SOURCE_ADDR_SUBUNIT: u16 = 0x000D;
17    pub const SOURCE_NETWORK_TYPE: u16 = 0x000E;
18    pub const SOURCE_BEARER_TYPE: u16 = 0x000F;
19    pub const SOURCE_TELEMATICS_ID: u16 = 0x0010;
20    pub const QOS_TIME_TO_LIVE: u16 = 0x0017;
21    pub const PAYLOAD_TYPE: u16 = 0x0019;
22    pub const ADDITIONAL_STATUS_INFO_TEXT: u16 = 0x001D;
23    pub const RECEIPTED_MESSAGE_ID: u16 = 0x001E;
24    pub const MS_MSG_WAIT_FACILITIES: u16 = 0x0030;
25    pub const PRIVACY_INDICATOR: u16 = 0x0201;
26    pub const SOURCE_SUBADDRESS: u16 = 0x0202;
27    pub const DEST_SUBADDRESS: u16 = 0x0203;
28    pub const USER_MESSAGE_REFERENCE: u16 = 0x0204;
29    pub const USER_RESPONSE_CODE: u16 = 0x0205;
30    pub const SOURCE_PORT: u16 = 0x020A;
31    pub const DESTINATION_PORT: u16 = 0x020B;
32    pub const SAR_MSG_REF_NUM: u16 = 0x020C;
33    pub const LANGUAGE_INDICATOR: u16 = 0x020D;
34    pub const SAR_TOTAL_SEGMENTS: u16 = 0x020E;
35    pub const SAR_SEGMENT_SEQNUM: u16 = 0x020F;
36    pub const SC_INTERFACE_VERSION: u16 = 0x0210;
37    pub const CALLBACK_NUM_PRES_IND: u16 = 0x0302;
38    pub const CALLBACK_NUM_ATAG: u16 = 0x0303;
39    pub const NUMBER_OF_MESSAGES: u16 = 0x0304;
40    pub const CALLBACK_NUM: u16 = 0x0381;
41    pub const DPF_RESULT: u16 = 0x0420;
42    pub const SET_DPF: u16 = 0x0421;
43    pub const MS_AVAILABILITY_STATUS: u16 = 0x0422;
44    pub const NETWORK_ERROR_CODE: u16 = 0x0423;
45    pub const MESSAGE_PAYLOAD: u16 = 0x0424;
46    pub const DELIVERY_FAILURE_REASON: u16 = 0x0425;
47    pub const MORE_MESSAGES_TO_SEND: u16 = 0x0426;
48    pub const MESSAGE_STATE: u16 = 0x0427;
49    pub const USSD_SERVICE_OP: u16 = 0x0501;
50    pub const DISPLAY_TIME: u16 = 0x1201;
51    pub const SMS_SIGNAL: u16 = 0x1203;
52    pub const MS_VALIDITY: u16 = 0x1204;
53    pub const ALERT_ON_MESSAGE_DELIVERY: u16 = 0x130C;
54    pub const ITS_REPLY_TYPE: u16 = 0x1380;
55    pub const ITS_SESSION_INFO: u16 = 0x1383;
56}
57
58/// Helper function to get tag hex code from name (e.g., "SAR_MSG_REF_NUM" -> 0x020C)
59/// Returns 0 if not found.
60pub fn get_tag_by_name(name: &str) -> u16 {
61    match name {
62        "dest_addr_subunit" | "DEST_ADDR_SUBUNIT" => tags::DEST_ADDR_SUBUNIT,
63        "dest_network_type" | "DEST_NETWORK_TYPE" => tags::DEST_NETWORK_TYPE,
64        "dest_bearer_type" | "DEST_BEARER_TYPE" => tags::DEST_BEARER_TYPE,
65        "dest_telematics_id" | "DEST_TELEMATICS_ID" => tags::DEST_TELEMATICS_ID,
66        "source_addr_subunit" | "SOURCE_ADDR_SUBUNIT" => tags::SOURCE_ADDR_SUBUNIT,
67        "source_network_type" | "SOURCE_NETWORK_TYPE" => tags::SOURCE_NETWORK_TYPE,
68        "source_bearer_type" | "SOURCE_BEARER_TYPE" => tags::SOURCE_BEARER_TYPE,
69        "source_telematics_id" | "SOURCE_TELEMATICS_ID" => tags::SOURCE_TELEMATICS_ID,
70        "qos_time_to_live" | "QOS_TIME_TO_LIVE" => tags::QOS_TIME_TO_LIVE,
71        "payload_type" | "PAYLOAD_TYPE" => tags::PAYLOAD_TYPE,
72        "additional_status_info_text" | "ADDITIONAL_STATUS_INFO_TEXT" => {
73            tags::ADDITIONAL_STATUS_INFO_TEXT
74        }
75        "receipted_message_id" | "RECEIPTED_MESSAGE_ID" => tags::RECEIPTED_MESSAGE_ID,
76        "ms_msg_wait_facilities" | "MS_MSG_WAIT_FACILITIES" => tags::MS_MSG_WAIT_FACILITIES,
77        "privacy_indicator" | "PRIVACY_INDICATOR" => tags::PRIVACY_INDICATOR,
78        "source_subaddress" | "SOURCE_SUBADDRESS" => tags::SOURCE_SUBADDRESS,
79        "dest_subaddress" | "DEST_SUBADDRESS" => tags::DEST_SUBADDRESS,
80        "user_message_reference" | "USER_MESSAGE_REFERENCE" => tags::USER_MESSAGE_REFERENCE,
81        "user_response_code" | "USER_RESPONSE_CODE" => tags::USER_RESPONSE_CODE,
82        "source_port" | "SOURCE_PORT" => tags::SOURCE_PORT,
83        "destination_port" | "DESTINATION_PORT" => tags::DESTINATION_PORT,
84        "sar_msg_ref_num" | "SAR_MSG_REF_NUM" => tags::SAR_MSG_REF_NUM,
85        "language_indicator" | "LANGUAGE_INDICATOR" => tags::LANGUAGE_INDICATOR,
86        "sar_total_segments" | "SAR_TOTAL_SEGMENTS" => tags::SAR_TOTAL_SEGMENTS,
87        "sar_segment_seqnum" | "SAR_SEGMENT_SEQNUM" => tags::SAR_SEGMENT_SEQNUM,
88        "sc_interface_version" | "SC_INTERFACE_VERSION" => tags::SC_INTERFACE_VERSION,
89        "callback_num_pres_ind" | "CALLBACK_NUM_PRES_IND" => tags::CALLBACK_NUM_PRES_IND,
90        "callback_num_atag" | "CALLBACK_NUM_ATAG" => tags::CALLBACK_NUM_ATAG,
91        "number_of_messages" | "NUMBER_OF_MESSAGES" => tags::NUMBER_OF_MESSAGES,
92        "callback_num" | "CALLBACK_NUM" => tags::CALLBACK_NUM,
93        "dpf_result" | "DPF_RESULT" => tags::DPF_RESULT,
94        "set_dpf" | "SET_DPF" => tags::SET_DPF,
95        "ms_availability_status" | "MS_AVAILABILITY_STATUS" => tags::MS_AVAILABILITY_STATUS,
96        "network_error_code" | "NETWORK_ERROR_CODE" => tags::NETWORK_ERROR_CODE,
97        "message_payload" | "MESSAGE_PAYLOAD" => tags::MESSAGE_PAYLOAD,
98        "delivery_failure_reason" | "DELIVERY_FAILURE_REASON" => tags::DELIVERY_FAILURE_REASON,
99        "more_messages_to_send" | "MORE_MESSAGES_TO_SEND" => tags::MORE_MESSAGES_TO_SEND,
100        "message_state" | "MESSAGE_STATE" => tags::MESSAGE_STATE,
101        "ussd_service_op" | "USSD_SERVICE_OP" => tags::USSD_SERVICE_OP,
102        "display_time" | "DISPLAY_TIME" => tags::DISPLAY_TIME,
103        "sms_signal" | "SMS_SIGNAL" => tags::SMS_SIGNAL,
104        "ms_validity" | "MS_VALIDITY" => tags::MS_VALIDITY,
105        "alert_on_message_delivery" | "ALERT_ON_MESSAGE_DELIVERY" => {
106            tags::ALERT_ON_MESSAGE_DELIVERY
107        }
108        "its_reply_type" | "ITS_REPLY_TYPE" => tags::ITS_REPLY_TYPE,
109        "its_session_info" | "ITS_SESSION_INFO" => tags::ITS_SESSION_INFO,
110        _ => 0,
111    }
112}
113
114#[derive(Debug, Clone, PartialEq)]
115pub struct Tlv {
116    pub tag: u16,
117    pub length: u16,
118    pub value: Vec<u8>,
119}
120
121impl Tlv {
122    pub fn new(tag: u16, value: Vec<u8>) -> Self {
123        Self {
124            tag,
125            length: value.len() as u16,
126            value,
127        }
128    }
129
130    /// Convenience: create a TLV using a string name for the tag
131    pub fn new_from_name(name: &str, value: Vec<u8>) -> Self {
132        let tag = get_tag_by_name(name);
133        Self::new(tag, value)
134    }
135
136    pub fn new_u8(tag: u16, val: u8) -> Self {
137        Self::new(tag, vec![val])
138    }
139
140    // Convenience: create u8 TLV from name
141    pub fn new_u8_from_name(name: &str, val: u8) -> Self {
142        Self::new(get_tag_by_name(name), vec![val])
143    }
144
145    pub fn new_u16(tag: u16, val: u16) -> Self {
146        Self::new(tag, val.to_be_bytes().to_vec())
147    }
148
149    // Convenience: create u16 TLV from name
150    pub fn new_u16_from_name(name: &str, val: u16) -> Self {
151        Self::new(get_tag_by_name(name), val.to_be_bytes().to_vec())
152    }
153
154    pub fn new_string(tag: u16, val: &str) -> Self {
155        let mut v = val.as_bytes().to_vec();
156        v.push(0); // Null terminator
157        Self::new(tag, v)
158    }
159
160    pub fn new_payload(tag: u16, val: Vec<u8>) -> Self {
161        Self::new(tag, val)
162    }
163
164    pub fn encode(&self, writer: &mut impl Write) -> Result<(), PduError> {
165        writer.write_all(&self.tag.to_be_bytes())?;
166        writer.write_all(&self.length.to_be_bytes())?;
167        writer.write_all(&self.value)?;
168        Ok(())
169    }
170
171    pub fn decode(cursor: &mut Cursor<&[u8]>) -> Result<Option<Self>, PduError> {
172        let pos = cursor.position();
173        let len = cursor.get_ref().len() as u64;
174
175        if pos + 4 > len {
176            return Ok(None);
177        }
178
179        let mut tag_bytes = [0u8; 2];
180        cursor.read_exact(&mut tag_bytes)?;
181        let tag = u16::from_be_bytes(tag_bytes);
182
183        let mut len_bytes = [0u8; 2];
184        cursor.read_exact(&mut len_bytes)?;
185        let length = u16::from_be_bytes(len_bytes);
186
187        let current_pos = cursor.position();
188        if current_pos + length as u64 > len {
189            return Err(PduError::BufferTooShort);
190        }
191
192        let mut value = vec![0u8; length as usize];
193        cursor.read_exact(&mut value)?;
194
195        Ok(Some(Self { tag, length, value }))
196    }
197
198    // --- Getters ---
199    pub fn value_as_u8(&self) -> Result<u8, PduError> {
200        if self.value.len() != 1 {
201            return Err(PduError::InvalidLength);
202        } // Assuming InvalidLength is in common
203        Ok(self.value[0])
204    }
205
206    pub fn value_as_u16(&self) -> Result<u16, PduError> {
207        if self.value.len() != 2 {
208            return Err(PduError::InvalidLength);
209        }
210        Ok(u16::from_be_bytes([self.value[0], self.value[1]]))
211    }
212
213    pub fn value_as_string(&self) -> Result<String, PduError> {
214        let v = if !self.value.is_empty() && self.value[self.value.len() - 1] == 0 {
215            &self.value[..self.value.len() - 1]
216        } else {
217            &self.value
218        };
219        String::from_utf8(v.to_vec()).map_err(PduError::Utf8)
220    }
221}