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
use crate::common::{read_c_string, write_c_string, BindMode, Npi, PduError, Ton, HEADER_LEN};
use std::io::{Cursor, Read, Write};
/// Represents a Bind Request PDU (Receiver, Transmitter, or Transceiver).
///
/// This PDU is used to initiate a session with the SMSC.
#[derive(Debug, Clone, PartialEq)]
pub struct BindRequest {
/// Sequence number of the PDU
pub sequence_number: u32,
/// Bind Mode (Receiver, Transmitter, Transceiver)
pub mode: BindMode,
/// System ID (Username)
pub system_id: String,
/// Password
pub password: String,
/// System Type (Verification of system type)
pub system_type: String,
/// Interface Version (e.g., 0x34 for SMPP 3.4)
pub interface_version: u8,
/// Address Type of Number (TON)
pub addr_ton: Ton,
/// Address Numbering Plan Indicator (NPI)
pub addr_npi: Npi,
/// Address Range (Regular Expression for routing)
pub address_range: String,
}
impl BindRequest {
/// Create a new Bind Request with defaults.
///
/// # Examples
///
/// ```
/// use smpp_codec::pdus::BindRequest;
/// use smpp_codec::common::BindMode;
///
/// let sequence_number: u32 = 1;
/// let bind_req = BindRequest::new(
/// sequence_number,
/// BindMode::Transceiver,
/// "system_id".to_string(),
/// "password".to_string(),
/// );
/// ```
pub fn new(sequence_number: u32, mode: BindMode, system_id: String, password: String) -> Self {
Self {
sequence_number,
mode,
system_id,
password,
system_type: String::new(),
interface_version: 0x34, // SMPP 3.4
addr_ton: Ton::Unknown,
addr_npi: Npi::Unknown,
address_range: String::new(),
}
}
/// Builder pattern helper to set the address range fields.
pub fn with_address_range(mut self, ton: Ton, npi: Npi, range: String) -> Self {
self.addr_ton = ton;
self.addr_npi = npi;
self.address_range = range;
self
}
/// Encode the struct into raw bytes for the network.
///
/// # Errors
///
/// Returns a [`PduError`] if:
/// * `system_id` exceeds 16 characters.
/// * `password` exceeds 9 characters.
/// * `system_type` exceeds 13 characters.
/// * `address_range` exceeds 41 characters.
/// * An I/O error occurs while writing.
///
/// # Examples
///
/// ```
/// # use smpp_codec::pdus::BindRequest;
/// # use smpp_codec::common::BindMode;
/// # let sequence_number: u32 = 1;
/// # let bind_req = BindRequest::new(sequence_number, BindMode::Transmitter, "id".into(), "pwd".into());
/// let mut buffer = Vec::new();
/// bind_req.encode(&mut buffer).expect("Encoding failed");
/// ```
pub fn encode(&self, writer: &mut impl Write) -> Result<(), PduError> {
// 1. Validate Constraints
if self.system_id.len() > 16 {
return Err(PduError::StringTooLong("system_id".into(), 16));
}
if self.password.len() > 9 {
return Err(PduError::StringTooLong("password".into(), 9));
}
if self.system_type.len() > 13 {
return Err(PduError::StringTooLong("system_type".into(), 13));
}
if self.address_range.len() > 41 {
return Err(PduError::StringTooLong("address_range".into(), 41));
}
// 2. Calculate Length Upfront
// Header (16) + SystemID (N+1) + Password (N+1) + SystemType (N+1) + Ver(1) + Ton(1) + Npi(1) + Range(N+1)
let body_len = self.system_id.len() + 1 +
self.password.len() + 1 +
self.system_type.len() + 1 +
1 + // interface_version
1 + // addr_ton
1 + // addr_npi
self.address_range.len() + 1;
let command_len = (HEADER_LEN + body_len) as u32;
// 3. Write Header
writer.write_all(&command_len.to_be_bytes())?;
writer.write_all(&self.mode.command_id().to_be_bytes())?;
writer.write_all(&0u32.to_be_bytes())?; // Command Status
writer.write_all(&self.sequence_number.to_be_bytes())?;
// 4. Write Body
write_c_string(writer, &self.system_id)?;
write_c_string(writer, &self.password)?;
write_c_string(writer, &self.system_type)?;
writer.write_all(&[self.interface_version])?;
writer.write_all(&[self.addr_ton as u8, self.addr_npi as u8])?;
write_c_string(writer, &self.address_range)?;
Ok(())
}
/// Decode raw bytes from the network into the struct.
///
/// # Errors
///
/// Returns a [`PduError`] if:
/// * The buffer is too short to contain a valid header.
/// * The command ID does not correspond to a Bind Request.
/// * The buffer data is malformed.
///
/// # Examples
///
/// ```
/// # use smpp_codec::pdus::BindRequest;
/// # use smpp_codec::common::BindMode;
/// # let bind_req = BindRequest::new(1, BindMode::Transmitter, "id".into(), "pwd".into());
/// # let mut buffer = Vec::new();
/// # bind_req.encode(&mut buffer).unwrap();
/// let decoded = BindRequest::decode(&buffer).expect("Decoding failed");
/// assert_eq!(decoded.system_id, "id");
/// ```
pub fn decode(buffer: &[u8]) -> Result<Self, PduError> {
// 1. Validate total length
if buffer.len() < HEADER_LEN {
return Err(PduError::BufferTooShort);
}
let mut cursor = Cursor::new(buffer);
// 2. Read Header
let mut bytes = [0u8; 4];
// Command Length
cursor.read_exact(&mut bytes)?;
let command_len = u32::from_be_bytes(bytes) as usize;
if buffer.len() != command_len {
// We can be strict or loose here. Strict is safer for libraries.
// return Err(PduError::InvalidLength);
}
// Command ID
cursor.read_exact(&mut bytes)?;
let command_id = u32::from_be_bytes(bytes);
// Map Command ID to BindMode
let mode = match command_id {
0x00000001 => BindMode::Receiver,
0x00000002 => BindMode::Transmitter,
0x00000009 => BindMode::Transceiver,
_ => return Err(PduError::InvalidCommandId(command_id)),
};
// Command Status
cursor.read_exact(&mut bytes)?;
let _command_status = u32::from_be_bytes(bytes);
// Sequence Number
cursor.read_exact(&mut bytes)?;
let sequence_number = u32::from_be_bytes(bytes);
// 3. Read Body (C-Strings and u8s)
let system_id = read_c_string(&mut cursor)?;
let password = read_c_string(&mut cursor)?;
let system_type = read_c_string(&mut cursor)?;
// Simple u8 reads
let mut u8_buf = [0u8; 1];
cursor.read_exact(&mut u8_buf)?;
let interface_version = u8_buf[0];
cursor.read_exact(&mut u8_buf)?;
let addr_ton = Ton::from(u8_buf[0]); // Convert byte -> Enum
cursor.read_exact(&mut u8_buf)?;
let addr_npi = Npi::from(u8_buf[0]); // Convert byte -> Enum
let address_range = read_c_string(&mut cursor)?;
Ok(Self {
sequence_number,
mode,
system_id,
password,
system_type,
interface_version,
addr_ton,
addr_npi,
address_range,
})
}
}