1use bitflags::bitflags;
4use bytes::Bytes;
5
6use crate::MapError;
7
8bitflags! {
9 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
11 pub struct MessageTypes: u8 {
12 const SMS_GSM = 0x01;
14 const SMS_CDMA = 0x02;
16 const EMAIL = 0x04;
18 const MMS = 0x08;
20 const IM = 0x10;
22 }
23}
24
25bitflags! {
26 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
28 pub struct FilterMessageType: u8 {
29 const SMS_GSM = 0x01;
31 const SMS_CDMA = 0x02;
33 const EMAIL = 0x04;
35 const MMS = 0x08;
37 }
38}
39
40pub mod tag {
42 pub const TRANSPARENT: u8 = 0x0A;
44 pub const RETRY: u8 = 0x0B;
46 pub const CHARSET: u8 = 0x14;
48 pub const FILTER_MESSAGE_TYPE: u8 = 0x03;
50 pub const FOLDER_LISTING_SIZE: u8 = 0x07;
52 pub(crate) const MAX_LIST_COUNT: u8 = 0x01;
53 pub(crate) const LIST_START_OFFSET: u8 = 0x02;
54 pub const NEW_MESSAGE: u8 = 0x0D;
56 pub const NOTIFICATION_STATUS: u8 = 0x0E;
58 pub(crate) const FILTER_PERIOD_BEGIN: u8 = 0x04;
59 pub(crate) const FILTER_PERIOD_END: u8 = 0x05;
60 pub(crate) const FILTER_READ_STATUS: u8 = 0x06;
61 pub(crate) const FILTER_ORIGINATOR: u8 = 0x08;
62 pub const MESSAGES_LISTING_SIZE: u8 = 0x12;
64 pub const STATUS_INDICATOR: u8 = 0x17;
66 pub const STATUS_VALUE: u8 = 0x18;
68}
69
70pub const CHARSET_UTF8: u8 = 0x01;
72
73pub const INDICATOR_READ_STATUS: u8 = 0x00;
75pub const INDICATOR_DELETED_STATUS: u8 = 0x01;
77
78#[must_use]
80pub const fn push_message_params() -> Bytes {
81 Bytes::from_static(b"\x14\x01\x01")
82}
83
84#[must_use]
86pub const fn get_message_params() -> Bytes {
87 Bytes::from_static(b"\x14\x01\x01")
88}
89
90#[must_use]
92pub fn set_message_status_params(indicator: u8, value: u8) -> Vec<u8> {
93 let mut params = Vec::with_capacity(6);
94 params.extend_from_slice(&[tag::STATUS_INDICATOR, 0x01, indicator]);
95 params.extend_from_slice(&[tag::STATUS_VALUE, 0x01, value]);
96 params
97}
98
99#[must_use]
101pub const fn set_notification_registration_params(enable: bool) -> Bytes {
102 if enable {
103 Bytes::from_static(b"\x0e\x01\x01\x0f\x01\x00")
104 } else {
105 Bytes::from_static(b"\x0e\x01\x00\x0f\x01\x00")
106 }
107}
108
109pub fn list_messages_params(
119 max_count: u16,
120 offset: u16,
121 read_status: Option<u8>,
122 originating_address: Option<&str>,
123 period_begin: Option<&str>,
124 period_end: Option<&str>,
125) -> Result<Vec<u8>, MapError> {
126 let mut cap = 8_usize;
127 if read_status.is_some() {
128 cap = cap.saturating_add(3);
129 }
130 for s in [originating_address, period_begin, period_end].into_iter().flatten() {
131 cap = cap.saturating_add(s.len().saturating_add(3));
132 }
133 let mut params = Vec::with_capacity(cap);
134 params.extend_from_slice(&[tag::MAX_LIST_COUNT, 0x02]);
135 params.extend_from_slice(&max_count.to_be_bytes());
136 params.extend_from_slice(&[tag::LIST_START_OFFSET, 0x02]);
137 params.extend_from_slice(&offset.to_be_bytes());
138 if let Some(status) = read_status {
139 params.extend_from_slice(&[tag::FILTER_READ_STATUS, 0x01, status]);
140 }
141 push_str_tlv(&mut params, tag::FILTER_ORIGINATOR, originating_address)?;
142 push_str_tlv(&mut params, tag::FILTER_PERIOD_BEGIN, period_begin)?;
143 push_str_tlv(&mut params, tag::FILTER_PERIOD_END, period_end)?;
144 Ok(params)
145}
146
147fn push_str_tlv(out: &mut Vec<u8>, tag_byte: u8, value: Option<&str>) -> Result<(), MapError> {
148 let Some(s) = value else {
149 return Ok(());
150 };
151 if s.contains('\x00') {
152 return Err(MapError::InvalidInput("filter string must not contain null bytes"));
153 }
154 let len = u8::try_from(s.len().saturating_add(1))
155 .map_err(|_| MapError::InvalidInput("filter string exceeds 254 UTF-8 bytes"))?;
156 out.push(tag_byte);
157 out.push(len);
158 out.extend_from_slice(s.as_bytes());
159 out.push(0x00);
160 Ok(())
161}
162
163#[cfg(test)]
164mod tests {
165 use super::list_messages_params;
166 use crate::MapError;
167
168 #[test]
169 fn list_messages_params_with_originator() -> Result<(), MapError> {
170 let bytes = list_messages_params(1024, 0, None, Some("5550001001"), None, None)?;
171 let tlv: &[u8] = b"\x08\x0b5550001001\x00";
172 assert!(bytes.windows(tlv.len()).any(|w| w == tlv));
173 Ok(())
174 }
175
176 #[test]
177 fn list_messages_params_with_period_filters() -> Result<(), MapError> {
178 let bytes = list_messages_params(
179 1024,
180 0,
181 None,
182 None,
183 Some("20260601T000000"),
184 Some("20260605T235959"),
185 )?;
186 let begin_tlv: &[u8] = b"\x04\x1020260601T000000\x00";
187 let end_tlv: &[u8] = b"\x05\x1020260605T235959\x00";
188 assert!(bytes.windows(begin_tlv.len()).any(|w| w == begin_tlv));
189 assert!(bytes.windows(end_tlv.len()).any(|w| w == end_tlv));
190 Ok(())
191 }
192
193 #[test]
194 fn list_messages_params_rejects_null_byte() {
195 let result = list_messages_params(1024, 0, None, Some("abc\x00def"), None, None);
196 assert!(matches!(result, Err(MapError::InvalidInput(_))));
197 }
198
199 #[test]
200 fn list_messages_params_rejects_overlong() {
201 let long: String = "a".repeat(255);
202 let result = list_messages_params(1024, 0, None, Some(&long), None, None);
203 assert!(matches!(result, Err(MapError::InvalidInput(_))));
204 }
205}