1use super::{
4 Alphabet, Dcs, Ie, PduError, Result, Udh, Validity, IEI_CONCAT_16, IEI_CONCAT_8,
5 IEI_LOCKING_SHIFT, IEI_SINGLE_SHIFT, TOA_ALPHANUMERIC, TOA_INTERNATIONAL, TOA_UNKNOWN,
6};
7use crate::sms::gsm7::{self, Language, Tables};
8use crate::sms::{hex_encode, ucs2};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Body {
13 Text(String),
14 Binary(Vec<u8>),
16}
17
18impl Default for Body {
19 fn default() -> Self {
20 Body::Text(String::new())
21 }
22}
23
24#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct SubmitRequest {
29 pub smsc: Option<String>,
31 pub smsc_toa: Option<u8>,
33 pub destination: String,
37 pub destination_toa: Option<u8>,
40 pub body: Body,
41 pub alphabet: Option<Alphabet>,
44 pub locking_shift: Vec<Language>,
46 pub single_shift: Vec<Language>,
48 pub class: Option<u8>,
49 pub pid: u8,
50 pub message_ref: u8,
51 pub status_report: bool,
52 pub reject_duplicates: bool,
53 pub reply_path: bool,
54 pub validity: Option<Validity>,
55 pub concat_ref: Option<u16>,
62 pub concat_16bit: bool,
63}
64
65impl SubmitRequest {
66 pub fn new(destination: impl Into<String>, text: impl Into<String>) -> Self {
67 SubmitRequest {
68 destination: destination.into(),
69 body: Body::Text(text.into()),
70 ..Default::default()
71 }
72 }
73}
74
75pub fn encode_address(value: &str, toa: Option<u8>) -> Result<Vec<u8>> {
78 let digits = value.strip_prefix('+');
79 if digits.unwrap_or(value).is_empty() {
80 return Err(PduError::Invalid("address"));
81 }
82 let toa = toa.unwrap_or(match digits {
83 Some(_) => TOA_INTERNATIONAL,
84 None if !value.is_empty()
85 && value
86 .bytes()
87 .all(|b| b.is_ascii_digit() || b == b'*' || b == b'#') =>
88 {
89 TOA_UNKNOWN
90 }
91 None => TOA_ALPHANUMERIC,
92 });
93 if super::Ton::from_toa(toa) == super::Ton::Alphanumeric {
94 let septets = gsm7::encode(value, Tables::DEFAULT).map_err(PduError::Unencodable)?;
95 let semi = (septets.len() * 7).div_ceil(4);
96 if semi > 20 {
97 return Err(PduError::Invalid("address length"));
98 }
99 let mut out = vec![semi as u8, toa];
100 out.extend(gsm7::pack(&septets, 0));
101 return Ok(out);
102 }
103 let digits = digits.unwrap_or(value);
104 if digits.chars().count() > 20 {
105 return Err(PduError::Invalid("address length"));
106 }
107 let body = super::encode_semi_octets(digits).map_err(PduError::Unencodable)?;
108 let mut out = vec![digits.len() as u8, toa];
109 out.extend(body);
110 Ok(out)
111}
112
113pub fn encode_smsc(value: Option<&str>, toa: Option<u8>) -> Result<Vec<u8>> {
115 let Some(v) = value.filter(|v| !v.is_empty()) else {
116 return Ok(vec![0]);
117 };
118 let digits = v.strip_prefix('+');
119 let toa = toa.unwrap_or(if digits.is_some() {
120 TOA_INTERNATIONAL
121 } else {
122 TOA_UNKNOWN
123 });
124 let digits = digits.unwrap_or(v);
125 if digits.chars().count() > 20 {
126 return Err(PduError::Invalid("SMSC length"));
127 }
128 let body = super::encode_semi_octets(digits).map_err(PduError::Unencodable)?;
129 let mut out = vec![body.len() as u8 + 1, toa];
130 out.extend(body);
131 Ok(out)
132}
133
134struct Units {
137 alphabet: Alphabet,
138 tables: Tables,
139 units: Vec<Vec<u8>>,
140}
141
142fn units(req: &SubmitRequest) -> Result<Units> {
143 let text = match &req.body {
144 Body::Binary(b) => {
145 if req.alphabet.is_some_and(|a| a != Alphabet::Bit8) {
146 return Err(PduError::Invalid("alphabet for binary body"));
147 }
148 return Ok(Units {
149 alphabet: Alphabet::Bit8,
150 tables: Tables::DEFAULT,
151 units: b.iter().map(|&x| vec![x]).collect(),
152 });
153 }
154 Body::Text(t) => t,
155 };
156 let gsm = |tables: Tables| -> Result<Units> {
157 let units = text
158 .chars()
159 .map(|c| {
160 gsm7::encode_char(c, tables)
161 .map(|(s, n)| s[..n].to_vec())
162 .ok_or(PduError::Unencodable(c))
163 })
164 .collect::<Result<_>>()?;
165 Ok(Units {
166 alphabet: Alphabet::Gsm7,
167 tables,
168 units,
169 })
170 };
171 let ucs = || Units {
172 alphabet: Alphabet::Ucs2,
173 tables: Tables::DEFAULT,
174 units: text
175 .chars()
176 .map(|c| ucs2::encode(c.encode_utf8(&mut [0; 4])))
177 .collect(),
178 };
179 match req.alphabet {
180 Some(Alphabet::Gsm7) => {
181 let tables = gsm7::choose_tables(text, &req.locking_shift, &req.single_shift)
182 .ok_or_else(|| first_unencodable(text))?;
183 gsm(tables)
184 }
185 Some(Alphabet::Ucs2) => Ok(ucs()),
186 Some(Alphabet::Bit8) => Err(PduError::Invalid("alphabet for text body")),
187 None => Ok(
188 match gsm7::choose_tables(text, &req.locking_shift, &req.single_shift) {
189 Some(t) => gsm(t)?,
190 None => ucs(),
191 },
192 ),
193 }
194}
195
196fn first_unencodable(text: &str) -> PduError {
199 gsm7::encode(text, Tables::DEFAULT)
200 .err()
201 .map_or(PduError::Invalid("text"), PduError::Unencodable)
202}
203
204fn capacity(alphabet: Alphabet, udh_len: usize) -> usize {
207 match alphabet {
208 Alphabet::Gsm7 => 160 - (udh_len * 8).div_ceil(7),
209 _ => 140 - udh_len,
210 }
211}
212
213pub fn encode_submit(req: &SubmitRequest) -> Result<Vec<(usize, String)>> {
219 let u = units(req)?;
220 let smsc = encode_smsc(req.smsc.as_deref(), req.smsc_toa)?;
221 let da = encode_address(&req.destination, req.destination_toa)?;
222
223 let mut national = Vec::new();
224 if u.tables.single != Language::Default {
225 national.push(Ie {
226 id: IEI_SINGLE_SHIFT,
227 data: vec![u.tables.single.nli()],
228 });
229 }
230 if u.tables.locking != Language::Default {
231 national.push(Ie {
232 id: IEI_LOCKING_SHIFT,
233 data: vec![u.tables.locking.nli()],
234 });
235 }
236 let national_len = national.iter().map(|ie| 2 + ie.data.len()).sum::<usize>();
237
238 let total: usize = u.units.iter().map(Vec::len).sum();
239 let single_udh = if national.is_empty() {
240 0
241 } else {
242 1 + national_len
243 };
244 let concat_ref = req.concat_ref.unwrap_or(0);
245 let wide = req.concat_16bit || concat_ref > 255;
246 let parts: Vec<&[Vec<u8>]> = if total <= capacity(u.alphabet, single_udh) {
247 vec![&u.units[..]]
248 } else {
249 let cap = capacity(u.alphabet, 1 + national_len + if wide { 6 } else { 5 });
250 let mut parts = Vec::new();
251 let (mut start, mut used) = (0, 0);
252 for (i, unit) in u.units.iter().enumerate() {
253 if used + unit.len() > cap {
254 parts.push(&u.units[start..i]);
255 (start, used) = (i, 0);
256 }
257 used += unit.len();
258 }
259 parts.push(&u.units[start..]);
260 parts
261 };
262 if parts.len() > 255 {
263 return Err(PduError::TooManyParts);
264 }
265 if parts.len() > 1 && req.concat_ref.is_none() {
266 return Err(PduError::MissingConcatRef);
267 }
268
269 let (vpf, vp) = match &req.validity {
270 None => (0, Vec::new()),
271 Some(v @ Validity::Relative(r)) => (v.vpf(), vec![*r]),
272 Some(v @ Validity::Absolute(t)) => (
273 v.vpf(),
274 t.encode().ok_or(PduError::Invalid("TP-VP"))?.to_vec(),
275 ),
276 Some(v @ Validity::Enhanced(b)) => (v.vpf(), b.to_vec()),
277 };
278 let dcs = Dcs::encode(u.alphabet, req.class);
279 let n = parts.len();
280 let mut out = Vec::with_capacity(n);
281 for (i, part) in parts.iter().enumerate() {
282 let mut ies = Vec::new();
283 if n > 1 {
284 let (seq, total) = ((i + 1) as u8, n as u8);
285 ies.push(if wide {
286 let r = concat_ref.to_be_bytes();
287 Ie {
288 id: IEI_CONCAT_16,
289 data: vec![r[0], r[1], total, seq],
290 }
291 } else {
292 Ie {
293 id: IEI_CONCAT_8,
294 data: vec![concat_ref as u8, total, seq],
295 }
296 });
297 }
298 ies.extend(national.iter().cloned());
299 let udh = (!ies.is_empty())
300 .then(|| Udh { ies }.encode())
301 .transpose()?;
302 let udh_len = udh.as_ref().map_or(0, Vec::len);
303 let body: Vec<u8> = part.concat();
304 let (udl, packed) = match u.alphabet {
305 Alphabet::Gsm7 => {
306 let hdr_septets = (udh_len * 8).div_ceil(7);
307 let fill = (hdr_septets * 7 - udh_len * 8) as u8;
308 (hdr_septets + body.len(), gsm7::pack(&body, fill))
309 }
310 _ => (udh_len + body.len(), body),
311 };
312 let fo = 0x01
313 | u8::from(req.reject_duplicates) << 2
314 | vpf << 3
315 | u8::from(req.status_report) << 5
316 | u8::from(udh.is_some()) << 6
317 | u8::from(req.reply_path) << 7;
318 let mut tpdu = vec![fo, req.message_ref];
319 tpdu.extend_from_slice(&da);
320 tpdu.extend_from_slice(&[req.pid, dcs]);
321 tpdu.extend_from_slice(&vp);
322 tpdu.push(udl as u8);
323 tpdu.extend(udh.unwrap_or_default());
324 tpdu.extend(packed);
325 let mut pdu = smsc.clone();
326 pdu.extend_from_slice(&tpdu);
327 out.push((tpdu.len(), hex_encode(&pdu)));
328 }
329 Ok(out)
330}