1pub const CMD_BIND_RECEIVER: u32 = 0x00000001;
10pub const CMD_BIND_RECEIVER_RESP: u32 = 0x80000001;
12
13pub const CMD_BIND_TRANSMITTER: u32 = 0x00000002;
15pub const CMD_BIND_TRANSMITTER_RESP: u32 = 0x80000002;
17
18pub const CMD_BIND_TRANSCEIVER: u32 = 0x00000009;
20pub const CMD_BIND_TRANSCEIVER_RESP: u32 = 0x80000009;
22
23pub const CMD_OUTBIND: u32 = 0x0000000B;
25
26pub const CMD_ENQUIRE_LINK: u32 = 0x00000015;
28pub const CMD_ENQUIRE_LINK_RESP: u32 = 0x80000015;
30
31pub const CMD_SUBMIT_SM: u32 = 0x00000004;
33pub const CMD_SUBMIT_SM_RESP: u32 = 0x80000004;
35
36pub const CMD_DELIVER_SM: u32 = 0x00000005;
38pub const CMD_DELIVER_SM_RESP: u32 = 0x80000005;
40
41pub const CMD_UNBIND: u32 = 0x00000006;
43pub const CMD_UNBIND_RESP: u32 = 0x80000006;
45
46pub const CMD_SUBMIT_MULTI_SM: u32 = 0x00000021;
48pub const CMD_SUBMIT_MULTI_SM_RESP: u32 = 0x80000021;
50
51pub const CMD_QUERY_SM: u32 = 0x00000022;
53pub const CMD_QUERY_SM_RESP: u32 = 0x80000022;
55
56pub const CMD_CANCEL_SM: u32 = 0x00000023;
58pub const CMD_CANCEL_SM_RESP: u32 = 0x80000023;
60
61pub const CMD_REPLACE_SM: u32 = 0x00000024;
63pub const CMD_REPLACE_SM_RESP: u32 = 0x80000024;
65
66pub const CMD_SUBMIT_SM_MULTI: u32 = 0x00000025;
68pub const CMD_SUBMIT_SM_MULTI_RESP: u32 = 0x80000025;
70
71pub const CMD_DATA_SM: u32 = 0x00000103;
73pub const CMD_DATA_SM_RESP: u32 = 0x80000103;
75
76pub const CMD_ALERT_NOTIFICATION: u32 = 0x00000102;
78pub const CMD_ALERT_NOTIFICATION_RESP: u32 = 0x80000102;
80
81pub const GENERIC_NACK: u32 = 0x80000000;
83
84pub const CMD_BROADCAST_SM: u32 = 0x00000111;
86pub const CMD_BROADCAST_SM_RESP: u32 = 0x80000112;
88
89pub const CMD_QUERY_BROADCAST_SM: u32 = 0x00000112;
91pub const CMD_QUERY_BROADCAST_SM_RESP: u32 = 0x80000112;
93pub const CMD_CANCEL_BROADCAST_SM: u32 = 0x00000113;
95pub const CMD_CANCEL_BROADCAST_SM_RESP: u32 = 0x80000113;
97
98pub const HEADER_LEN: usize = 16;
100
101pub const SMPP_INTERFACE_VERSION_34: u8 = 0x34;
103pub const SMPP_INTERFACE_VERSION_50: u8 = 0x50;
105
106#[derive(Debug, Clone, Copy, PartialEq)]
108#[repr(u8)]
109pub enum Ton {
110 Unknown = 0x00,
112 International = 0x01,
114 National = 0x02,
116 NetworkSpecific = 0x03,
118 SubscriberNumber = 0x04,
120 Alphanumeric = 0x05,
122 Abbreviated = 0x06,
124}
125
126impl From<u8> for Ton {
127 fn from(value: u8) -> Self {
128 match value {
129 0x01 => Ton::International,
130 0x02 => Ton::National,
131 0x03 => Ton::NetworkSpecific,
132 0x04 => Ton::SubscriberNumber,
133 0x05 => Ton::Alphanumeric,
134 0x06 => Ton::Abbreviated,
135 _ => Ton::Unknown,
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq)]
142#[repr(u8)]
143pub enum Npi {
144 Unknown = 0x00,
146 Isdn = 0x01,
148 Data = 0x03,
150 Telex = 0x04,
152 LandMobile = 0x06,
154 National = 0x08,
156 Private = 0x09,
158 Ermes = 0x0A,
160 Internet = 0x0E,
162 Wap = 0x12,
164}
165
166impl From<u8> for Npi {
167 fn from(value: u8) -> Self {
168 match value {
169 0x01 => Npi::Isdn,
170 0x03 => Npi::Data,
171 0x04 => Npi::Telex,
172 0x06 => Npi::LandMobile,
173 0x08 => Npi::National,
174 0x09 => Npi::Private,
175 0x0A => Npi::Ermes,
176 0x0E => Npi::Internet,
177 0x12 => Npi::Wap,
178 _ => Npi::Unknown,
179 }
180 }
181}
182
183pub const COMMAND_STATUS_OK: u32 = 0x00000000;
185
186pub fn get_status_description(status: u32) -> String {
188 match status {
189 0x00000000 => "ESME_ROK".to_string(),
190 0x00000001 => "ESME_RINVMSGLEN".to_string(),
191 0x00000002 => "ESME_RINVCMDLEN".to_string(),
192 0x00000003 => "ESME_RINVCMDID".to_string(),
193 0x00000004 => "ESME_RINVBNDSTS".to_string(),
194 0x00000005 => "ESME_RALYBND".to_string(),
195 0x00000006 => "ESME_RINVPRTFLG".to_string(),
196 0x00000007 => "ESME_RINVREGDLVFLG".to_string(),
197 0x00000008 => "ESME_RSYSERR".to_string(),
198 0x0000000A => "ESME_RINVSRCADR".to_string(),
199 0x0000000B => "ESME_RINVDSTADR".to_string(),
200 0x0000000C => "ESME_RINVMSGID".to_string(),
201 0x0000000D => "ESME_RBINDFAIL".to_string(),
202 0x0000000E => "ESME_RINVPASWD".to_string(),
203 0x0000000F => "ESME_RINVSYSID".to_string(),
204 0x00000011 => "ESME_RCANCELFAIL".to_string(),
205 0x00000013 => "ESME_RREPLACEFAIL".to_string(),
206 0x00000014 => "ESME_RMSGQFUL".to_string(),
207 0x00000015 => "ESME_RINVSERVICETYPE".to_string(),
208 0x00000033 => "ESME_RINVNUMDESTS".to_string(),
209 0x00000034 => "ESME_RINVDLNAME".to_string(),
210 0x00000040 => "ESME_RINVDESTFLAG".to_string(),
211 0x00000042 => "ESME_RINVSUBREP".to_string(),
212 0x00000043 => "ESME_RINVESMCLASS".to_string(),
213 0x00000044 => "ESME_RCNTSUBDL".to_string(),
214 0x00000045 => "ESME_RSUBMITFAIL".to_string(),
215 0x00000048 => "ESME_RINVSRCTON".to_string(),
216 0x00000049 => "ESME_RINVSRCNPI".to_string(),
217 0x00000050 => "ESME_RINVDSTTON".to_string(),
218 0x00000051 => "ESME_RINVDSTNPI".to_string(),
219 0x00000053 => "ESME_RINVSYSTYP".to_string(),
220 0x00000054 => "ESME_RINVREPFLAG".to_string(),
221 0x00000055 => "ESME_RINVNUMMSGS".to_string(),
222 0x00000058 => "ESME_RTHROTTLED".to_string(),
223 0x00000061 => "ESME_RINVSCHED".to_string(),
224 0x00000062 => "ESME_RINVEXPIRY".to_string(),
225 0x00000063 => "ESME_RINVDFTMSGID".to_string(),
226 0x00000064 => "ESME_RX_T_APPN".to_string(),
227 0x00000065 => "ESME_RX_P_APPN".to_string(),
228 0x00000066 => "ESME_RX_R_APPN".to_string(),
229 0x00000067 => "ESME_RQUERYFAIL".to_string(),
230 0x000000C0 => "ESME_RINVOPTPARSTREAM".to_string(),
231 0x000000C1 => "ESME_ROPTPARNOTALLWD".to_string(),
232 0x000000C2 => "ESME_RINVPARLEN".to_string(),
233 0x000000C3 => "ESME_RMISSINGOPTPARAM".to_string(),
234 0x000000C4 => "ESME_RINVOPTPARAMVAL".to_string(),
235 0x000000FE => "ESME_RDELIVERYFAILURE".to_string(),
236 0x000000FF => "ESME_RUNKNOWNERR".to_string(),
237 _ => format!("Unknown Error: 0x{:08X}", status),
238 }
239}
240
241pub fn get_status_code(name: &str) -> u32 {
243 match name {
244 "ESME_ROK" => 0x00000000,
245 "ESME_RINVMSGLEN" => 0x00000001,
246 "ESME_RINVCMDLEN" => 0x00000002,
247 "ESME_RINVCMDID" => 0x00000003,
248 "ESME_RINVBNDSTS" => 0x00000004,
249 "ESME_RALYBND" => 0x00000005,
250 "ESME_RINVPRTFLG" => 0x00000006,
251 "ESME_RINVREGDLVFLG" => 0x00000007,
252 "ESME_RSYSERR" => 0x00000008,
253 "ESME_RINVSRCADR" => 0x0000000A,
254 "ESME_RINVDSTADR" => 0x0000000B,
255 "ESME_RINVMSGID" => 0x0000000C,
256 "ESME_RBINDFAIL" => 0x0000000D,
257 "ESME_RINVPASWD" => 0x0000000E,
258 "ESME_RINVSYSID" => 0x0000000F,
259 "ESME_RCANCELFAIL" => 0x00000011,
260 "ESME_RREPLACEFAIL" => 0x00000013,
261 "ESME_RMSGQFUL" => 0x00000014,
262 "ESME_RINVSERVICETYPE" => 0x00000015,
263 "ESME_RINVNUMDESTS" => 0x00000033,
264 "ESME_RINVDLNAME" => 0x00000034,
265 "ESME_RINVDESTFLAG" => 0x00000040,
266 "ESME_RINVSUBREP" => 0x00000042,
267 "ESME_RINVESMCLASS" => 0x00000043,
268 "ESME_RCNTSUBDL" => 0x00000044,
269 "ESME_RSUBMITFAIL" => 0x00000045,
270 "ESME_RINVSRCTON" => 0x00000048,
271 "ESME_RINVSRCNPI" => 0x00000049,
272 "ESME_RINVDSTTON" => 0x00000050,
273 "ESME_RINVDSTNPI" => 0x00000051,
274 "ESME_RINVSYSTYP" => 0x00000053,
275 "ESME_RINVREPFLAG" => 0x00000054,
276 "ESME_RINVNUMMSGS" => 0x00000055,
277 "ESME_RTHROTTLED" => 0x00000058,
278 "ESME_RINVSCHED" => 0x00000061,
279 "ESME_RINVEXPIRY" => 0x00000062,
280 "ESME_RINVDFTMSGID" => 0x00000063,
281 "ESME_RX_T_APPN" => 0x00000064,
282 "ESME_RX_P_APPN" => 0x00000065,
283 "ESME_RX_R_APPN" => 0x00000066,
284 "ESME_RQUERYFAIL" => 0x00000067,
285 "ESME_RINVOPTPARSTREAM" => 0x000000C0,
286 "ESME_ROPTPARNOTALLWD" => 0x000000C1,
287 "ESME_RINVPARLEN" => 0x000000C2,
288 "ESME_RMISSINGOPTPARAM" => 0x000000C3,
289 "ESME_RINVOPTPARAMVAL" => 0x000000C4,
290 "ESME_RDELIVERYFAILURE" => 0x000000FE,
291 "ESME_RUNKNOWNERR" => 0x000000FF,
292 _ => 0x000000FF, }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq)]
298pub enum BindMode {
299 Receiver,
301 Transmitter,
303 Transceiver,
305}
306
307impl BindMode {
308 pub fn command_id(&self) -> u32 {
310 match self {
311 BindMode::Receiver => CMD_BIND_RECEIVER,
312 BindMode::Transmitter => CMD_BIND_TRANSMITTER,
313 BindMode::Transceiver => CMD_BIND_TRANSCEIVER,
314 }
315 }
316}
317
318#[derive(Debug)]
321pub enum PduError {
322 Io(std::io::Error),
324 Utf8(std::string::FromUtf8Error),
326 BufferTooShort,
328 InvalidCommandId(u32),
330 StringTooLong(String, usize), InvalidLength,
334}
335
336impl From<std::io::Error> for PduError {
338 fn from(err: std::io::Error) -> Self {
339 PduError::Io(err)
340 }
341}
342
343impl std::fmt::Display for PduError {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 match self {
346 PduError::Io(err) => write!(f, "IO Error: {}", err),
347 PduError::Utf8(err) => write!(f, "UTF8 Error: {}", err),
348 PduError::BufferTooShort => write!(f, "Buffer contains insufficient data"),
349 PduError::InvalidCommandId(id) => write!(f, "Invalid Command ID: 0x{:08X}", id),
350 PduError::StringTooLong(field, max) => {
351 write!(f, "String too long for field '{}' (max: {})", field, max)
352 }
353 PduError::InvalidLength => write!(f, "Invalid length for field"),
354 }
355 }
356}
357
358impl std::error::Error for PduError {
359 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
360 match self {
361 PduError::Io(err) => Some(err),
362 PduError::Utf8(err) => Some(err),
363 _ => None,
364 }
365 }
366}
367
368pub fn read_c_string(cursor: &mut std::io::Cursor<&[u8]>) -> Result<String, PduError> {
370 let current_pos = cursor.position() as usize;
371 let inner = cursor.get_ref();
372
373 if current_pos >= inner.len() {
374 return Err(PduError::Io(std::io::Error::from(
375 std::io::ErrorKind::UnexpectedEof,
376 )));
377 }
378
379 let remaining = &inner[current_pos..];
380
381 match remaining.iter().position(|&b| b == 0) {
383 Some(null_idx) => {
384 let s_bytes = &remaining[..null_idx];
385 let s = String::from_utf8(s_bytes.to_vec()).map_err(PduError::Utf8)?;
386 cursor.set_position((current_pos + null_idx + 1) as u64);
387 Ok(s)
388 }
389 None => Err(PduError::Io(std::io::Error::from(
390 std::io::ErrorKind::UnexpectedEof,
391 ))),
392 }
393}
394
395pub fn write_c_string(w: &mut impl std::io::Write, s: &str) -> std::io::Result<()> {
397 w.write_all(s.as_bytes())?;
398 w.write_all(&[0])
399}