smpp_codec/pdus/session_pdus/bind_request.rs
1use crate::common::{BindMode, Npi, PduError, Ton, HEADER_LEN};
2use std::io::{Cursor, Read, Write};
3
4/// Represents a Bind Request PDU (Receiver, Transmitter, or Transceiver).
5///
6/// This PDU is used to initiate a session with the SMSC.
7#[derive(Debug, Clone)]
8pub struct BindRequest {
9 pub sequence_number: u32,
10 pub mode: BindMode,
11 pub system_id: String,
12 pub password: String,
13 pub system_type: String,
14 pub interface_version: u8,
15 pub addr_ton: Ton,
16 pub addr_npi: Npi,
17 pub address_range: String,
18}
19
20impl BindRequest {
21 /// Create a new Bind Request with defaults.
22 ///
23 /// # Examples
24 ///
25 /// ```
26 /// use smpp_codec::pdus::BindRequest;
27 /// use smpp_codec::common::BindMode;
28 ///
29 /// let sequence_number: u32 = 1;
30 /// let bind_req = BindRequest::new(
31 /// sequence_number,
32 /// BindMode::Transceiver,
33 /// "system_id".to_string(),
34 /// "password".to_string(),
35 /// );
36 /// ```
37 pub fn new(sequence_number: u32, mode: BindMode, system_id: String, password: String) -> Self {
38 Self {
39 sequence_number,
40 mode,
41 system_id,
42 password,
43 system_type: String::new(),
44 interface_version: 0x34, // SMPP 3.4
45 addr_ton: Ton::Unknown,
46 addr_npi: Npi::Unknown,
47 address_range: String::new(),
48 }
49 }
50
51 pub fn with_address_range(mut self, ton: Ton, npi: Npi, range: String) -> Self {
52 self.addr_ton = ton;
53 self.addr_npi = npi;
54 self.address_range = range;
55 self
56 }
57
58 /// Encode the struct into raw bytes for the network.
59 ///
60 /// # Errors
61 ///
62 /// Returns a [`PduError`] if:
63 /// * `system_id` exceeds 16 characters.
64 /// * `password` exceeds 9 characters.
65 /// * `system_type` exceeds 13 characters.
66 /// * `address_range` exceeds 41 characters.
67 /// * An I/O error occurs while writing.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// # use smpp_codec::pdus::BindRequest;
73 /// # use smpp_codec::common::BindMode;
74 /// # let sequence_number: u32 = 1;
75 /// # let bind_req = BindRequest::new(sequence_number, BindMode::Transmitter, "id".into(), "pwd".into());
76 /// let mut buffer = Vec::new();
77 /// bind_req.encode(&mut buffer).expect("Encoding failed");
78 /// ```
79 pub fn encode(&self, writer: &mut impl Write) -> Result<(), PduError> {
80 // 1. Validate Constraints
81 if self.system_id.len() > 16 {
82 return Err(PduError::StringTooLong("system_id".into(), 16));
83 }
84 if self.password.len() > 9 {
85 return Err(PduError::StringTooLong("password".into(), 9));
86 }
87 if self.system_type.len() > 13 {
88 return Err(PduError::StringTooLong("system_type".into(), 13));
89 }
90
91 if self.address_range.len() > 41 {
92 return Err(PduError::StringTooLong("address_range".into(), 41));
93 }
94
95 // 2. Calculate Length Upfront
96 // Header (16) + SystemID (N+1) + Password (N+1) + SystemType (N+1) + Ver(1) + Ton(1) + Npi(1) + Range(N+1)
97 let body_len = self.system_id.len() + 1 +
98 self.password.len() + 1 +
99 self.system_type.len() + 1 +
100 1 + // interface_version
101 1 + // addr_ton
102 1 + // addr_npi
103 self.address_range.len() + 1;
104
105 let command_len = (HEADER_LEN + body_len) as u32;
106
107 // 3. Write Header
108 writer.write_all(&command_len.to_be_bytes())?;
109 writer.write_all(&self.mode.command_id().to_be_bytes())?;
110 writer.write_all(&0u32.to_be_bytes())?; // Command Status
111 writer.write_all(&self.sequence_number.to_be_bytes())?;
112
113 // 4. Write Body
114 write_c_string(writer, &self.system_id)?;
115 write_c_string(writer, &self.password)?;
116 write_c_string(writer, &self.system_type)?;
117 writer.write_all(&[self.interface_version])?;
118 writer.write_all(&[self.addr_ton as u8, self.addr_npi as u8])?;
119 write_c_string(writer, &self.address_range)?;
120
121 Ok(())
122 }
123
124 /// Decode raw bytes from the network into the struct.
125 ///
126 /// # Errors
127 ///
128 /// Returns a [`PduError`] if:
129 /// * The buffer is too short to contain a valid header.
130 /// * The command ID does not correspond to a Bind Request.
131 /// * The buffer data is malformed.
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// # use smpp_codec::pdus::BindRequest;
137 /// # use smpp_codec::common::BindMode;
138 /// # let bind_req = BindRequest::new(1, BindMode::Transmitter, "id".into(), "pwd".into());
139 /// # let mut buffer = Vec::new();
140 /// # bind_req.encode(&mut buffer).unwrap();
141 /// let decoded = BindRequest::decode(&buffer).expect("Decoding failed");
142 /// assert_eq!(decoded.system_id, "id");
143 /// ```
144 pub fn decode(buffer: &[u8]) -> Result<Self, PduError> {
145 // 1. Validate total length
146 if buffer.len() < HEADER_LEN {
147 return Err(PduError::BufferTooShort);
148 }
149
150 let mut cursor = Cursor::new(buffer);
151
152 // 2. Read Header
153 let mut bytes = [0u8; 4];
154
155 // Command Length
156 cursor.read_exact(&mut bytes)?;
157 let command_len = u32::from_be_bytes(bytes) as usize;
158
159 if buffer.len() != command_len {
160 // We can be strict or loose here. Strict is safer for libraries.
161 // return Err(PduError::InvalidLength);
162 }
163
164 // Command ID
165 cursor.read_exact(&mut bytes)?;
166 let command_id = u32::from_be_bytes(bytes);
167
168 // Map Command ID to BindMode
169 let mode = match command_id {
170 0x00000001 => BindMode::Receiver,
171 0x00000002 => BindMode::Transmitter,
172 0x00000009 => BindMode::Transceiver,
173 _ => return Err(PduError::InvalidCommandId(command_id)),
174 };
175
176 // Command Status
177 cursor.read_exact(&mut bytes)?;
178 let _command_status = u32::from_be_bytes(bytes);
179
180 // Sequence Number
181 cursor.read_exact(&mut bytes)?;
182 let sequence_number = u32::from_be_bytes(bytes);
183
184 // 3. Read Body (C-Strings and u8s)
185 let system_id = crate::common::read_c_string(&mut cursor)?;
186 let password = crate::common::read_c_string(&mut cursor)?;
187 let system_type = crate::common::read_c_string(&mut cursor)?;
188
189 // Simple u8 reads
190 let mut u8_buf = [0u8; 1];
191 cursor.read_exact(&mut u8_buf)?;
192 let interface_version = u8_buf[0];
193
194 cursor.read_exact(&mut u8_buf)?;
195 let addr_ton = Ton::from(u8_buf[0]); // Convert byte -> Enum
196
197 cursor.read_exact(&mut u8_buf)?;
198 let addr_npi = Npi::from(u8_buf[0]); // Convert byte -> Enum
199
200 let address_range = crate::common::read_c_string(&mut cursor)?;
201
202 Ok(Self {
203 sequence_number,
204 mode,
205 system_id,
206 password,
207 system_type,
208 interface_version,
209 addr_ton,
210 addr_npi,
211 address_range,
212 })
213 }
214}
215
216// --- Helpers (Private to this module) ---
217
218fn write_c_string(w: &mut impl Write, s: &str) -> std::io::Result<()> {
219 w.write_all(s.as_bytes())?;
220 w.write_all(&[0])
221}