1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
use crate::common::{
read_c_string, write_c_string, Npi, PduError, Ton, CMD_SUBMIT_MULTI_SM, HEADER_LEN,
};
use crate::tlv::Tlv;
use std::io::Read;
use std::io::{Cursor, Write};
#[derive(Debug, Clone, PartialEq)]
/// Destination for Submit Multi (SME Address or Distribution List)
pub enum Destination {
/// Normal SME Address
SmeAddress {
/// Type of Number
ton: Ton,
/// Numbering Plan Indicator
npi: Npi,
/// Address
address: String,
},
/// Distribution List Name
DistributionList(String),
}
/// Represents a Submit Multi PDU.
///
/// Used to submit a short message to multiple recipients (SME addresses or Distribution Lists).
#[derive(Debug, Clone, PartialEq)]
pub struct SubmitMulti {
/// Sequence number of the PDU
pub sequence_number: u32,
/// Service Type
pub service_type: String,
/// Source Address Type of Number
pub source_addr_ton: Ton,
/// Source Address Numbering Plan Indicator
pub source_addr_npi: Npi,
/// Source Address
pub source_addr: String,
/// List of Destinations (Max 255)
pub destinations: Vec<Destination>,
/// ESM Class
pub esm_class: u8,
/// Protocol Identifier
pub protocol_id: u8,
/// Priority Level
pub priority_flag: u8,
/// Scheduled Delivery Time
pub schedule_delivery_time: String,
/// Validity Period
pub validity_period: String,
/// Registered Delivery
pub registered_delivery: u8,
/// Replace If Present Flag
pub replace_if_present_flag: u8,
/// Data Coding Scheme
pub data_coding: u8,
/// SMSC Default Message ID
pub sm_default_msg_id: u8,
/// Short Message Data
pub short_message: Vec<u8>,
/// Optional Parameters (TLVs)
pub optional_params: Vec<Tlv>,
}
impl SubmitMulti {
/// Create a new Submit Multi PDU.
///
/// # Examples
///
/// ```
/// use smpp_codec::pdus::{SubmitMulti, Destination};
/// use smpp_codec::common::{Ton, Npi};
///
/// let dest1 = Destination::SmeAddress {
/// ton: Ton::International,
/// npi: Npi::Isdn,
/// address: "1234567890".to_string(),
/// };
/// let dest2 = Destination::DistributionList("MyList".to_string());
///
/// let pdu = SubmitMulti::new(
/// 1,
/// "Source".to_string(),
/// vec![dest1, dest2],
/// b"Hello World".to_vec(),
/// );
/// ```
pub fn new(
sequence_number: u32,
source_addr: String,
destinations: Vec<Destination>,
short_message: Vec<u8>,
) -> Self {
Self {
sequence_number,
service_type: String::new(),
source_addr_ton: Ton::Unknown,
source_addr_npi: Npi::Unknown,
source_addr,
destinations,
esm_class: 0,
protocol_id: 0,
priority_flag: 0,
schedule_delivery_time: String::new(),
validity_period: String::new(),
registered_delivery: 0, // Default: Don't request delivery receipt
replace_if_present_flag: 0,
data_coding: 0, // Default: SMSC Default
sm_default_msg_id: 0,
short_message,
optional_params: Vec::new(),
}
}
/// Encode the PDU into the writer.
///
/// # Errors
///
/// Returns a [`PduError`] if the write fails or fields are invalid.
pub fn encode(&self, writer: &mut impl Write) -> Result<(), PduError> {
// Calculate length of destinations
let mut dest_len = 0;
for dest in &self.destinations {
match dest {
Destination::SmeAddress { address, .. } => {
// Flag(1) + Ton(1) + Npi(1) + Address(N+1)
dest_len += 1 + 1 + 1 + address.len() + 1;
}
Destination::DistributionList(name) => {
// Flag(1) + Name(N+1)
dest_len += 1 + name.len() + 1;
}
}
}
// Calculate length of TLVs
let tlvs_len: usize = self
.optional_params
.iter()
.map(|tlv| 4 + tlv.length as usize)
.sum();
// Calculate Body Length
// ServiceType(N+1)
// SrcTon(1) + SrcNpi(1) + SrcAddr(N+1)
// DestCount(1) + DestLen
// Flags(3)
// Sched(N+1) + Validity(N+1)
// Reg(1) + Rep(1) + DC(1) + MsgId(1)
// SmLen(1) + SmData(N)
// TLVs
let body_len = self.service_type.len() + 1 +
1 + 1 + self.source_addr.len() + 1 +
1 + dest_len +
1 + 1 + 1 + // esm_class, protocol_id, priority_flag
self.schedule_delivery_time.len() + 1 +
self.validity_period.len() + 1 +
1 + 1 + 1 + 1 + // reg, rep, dc, id
1 + self.short_message.len() +
tlvs_len;
let command_len = (HEADER_LEN + body_len) as u32;
writer.write_all(&command_len.to_be_bytes())?;
writer.write_all(&CMD_SUBMIT_MULTI_SM.to_be_bytes())?;
writer.write_all(&0u32.to_be_bytes())?;
writer.write_all(&self.sequence_number.to_be_bytes())?;
write_c_string(writer, &self.service_type)?;
writer.write_all(&[self.source_addr_ton as u8, self.source_addr_npi as u8])?;
write_c_string(writer, &self.source_addr)?;
// Standard: "number_of_dests"
// We calculate it dynamically from the Vector length
if self.destinations.len() > 255 {
// Optional: Return error if > 255, as spec limits it.
// For now, casting to u8 is the simple approach.
}
writer.write_all(&[self.destinations.len() as u8])?;
for dest in &self.destinations {
match dest {
Destination::SmeAddress { ton, npi, address } => {
writer.write_all(&[1])?; // Dest Flag 1 = SME Address
writer.write_all(&[*ton as u8, *npi as u8])?;
write_c_string(writer, address)?;
}
Destination::DistributionList(name) => {
writer.write_all(&[2])?; // Dest Flag 2 = Distribution List
write_c_string(writer, name)?;
}
}
}
writer.write_all(&[self.esm_class, self.protocol_id, self.priority_flag])?;
write_c_string(writer, &self.schedule_delivery_time)?;
write_c_string(writer, &self.validity_period)?;
writer.write_all(&[
self.registered_delivery,
self.replace_if_present_flag,
self.data_coding,
self.sm_default_msg_id,
self.short_message.len() as u8,
])?;
writer.write_all(&self.short_message)?;
for tlv in &self.optional_params {
tlv.encode(writer)?;
}
Ok(())
}
/// Decode the PDU from the buffer.
///
/// # Errors
///
/// Returns a [`PduError`] if the buffer is too short or malformed.
pub fn decode(buffer: &[u8]) -> Result<Self, PduError> {
if buffer.len() < HEADER_LEN {
return Err(PduError::BufferTooShort);
}
let mut cursor = Cursor::new(buffer);
cursor.set_position(12); // Skip Header fields (Len, ID, Status)
let mut bytes = [0u8; 4];
cursor.read_exact(&mut bytes)?;
let sequence_number = u32::from_be_bytes(bytes);
let service_type = read_c_string(&mut cursor)?;
let mut u8_buf = [0u8; 1];
cursor.read_exact(&mut u8_buf)?;
let source_addr_ton = Ton::from(u8_buf[0]);
cursor.read_exact(&mut u8_buf)?;
let source_addr_npi = Npi::from(u8_buf[0]);
let source_addr = read_c_string(&mut cursor)?;
// Decode Destination List
cursor.read_exact(&mut u8_buf)?;
let dest_count = u8_buf[0];
let mut destinations = Vec::with_capacity(dest_count as usize);
for _ in 0..dest_count {
cursor.read_exact(&mut u8_buf)?;
let dest_flag = u8_buf[0];
if dest_flag == 1 {
// SME Address
cursor.read_exact(&mut u8_buf)?;
let ton = Ton::from(u8_buf[0]);
cursor.read_exact(&mut u8_buf)?;
let npi = Npi::from(u8_buf[0]);
let address = read_c_string(&mut cursor)?;
destinations.push(Destination::SmeAddress { ton, npi, address });
} else {
// Distribution List
let dl_name = read_c_string(&mut cursor)?;
destinations.push(Destination::DistributionList(dl_name));
}
}
cursor.read_exact(&mut u8_buf)?;
let esm_class = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let protocol_id = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let priority_flag = u8_buf[0];
let schedule_delivery_time = read_c_string(&mut cursor)?;
let validity_period = read_c_string(&mut cursor)?;
cursor.read_exact(&mut u8_buf)?;
let registered_delivery = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let replace_if_present_flag = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let data_coding = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let sm_default_msg_id = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let sm_length = u8_buf[0] as usize;
let mut short_message = vec![0u8; sm_length];
cursor.read_exact(&mut short_message)?;
let mut optional_params = Vec::new();
while let Some(tlv) = Tlv::decode(&mut cursor)? {
optional_params.push(tlv);
}
Ok(Self {
sequence_number,
service_type,
source_addr_ton,
source_addr_npi,
source_addr,
destinations,
esm_class,
protocol_id,
priority_flag,
schedule_delivery_time,
validity_period,
registered_delivery,
replace_if_present_flag,
data_coding,
sm_default_msg_id,
short_message,
optional_params,
})
}
}