use bitflags::bitflags;
use bytes::Bytes;
use crate::MapError;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MessageTypes: u8 {
const SMS_GSM = 0x01;
const SMS_CDMA = 0x02;
const EMAIL = 0x04;
const MMS = 0x08;
const IM = 0x10;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilterMessageType: u8 {
const SMS_GSM = 0x01;
const SMS_CDMA = 0x02;
const EMAIL = 0x04;
const MMS = 0x08;
}
}
pub mod tag {
pub const TRANSPARENT: u8 = 0x0A;
pub const RETRY: u8 = 0x0B;
pub const CHARSET: u8 = 0x14;
pub const FILTER_MESSAGE_TYPE: u8 = 0x03;
pub const FOLDER_LISTING_SIZE: u8 = 0x07;
pub(crate) const MAX_LIST_COUNT: u8 = 0x01;
pub(crate) const LIST_START_OFFSET: u8 = 0x02;
pub const NEW_MESSAGE: u8 = 0x0D;
pub const NOTIFICATION_STATUS: u8 = 0x0E;
pub(crate) const FILTER_PERIOD_BEGIN: u8 = 0x04;
pub(crate) const FILTER_PERIOD_END: u8 = 0x05;
pub(crate) const FILTER_READ_STATUS: u8 = 0x06;
pub(crate) const FILTER_ORIGINATOR: u8 = 0x08;
pub const MESSAGES_LISTING_SIZE: u8 = 0x12;
pub const STATUS_INDICATOR: u8 = 0x17;
pub const STATUS_VALUE: u8 = 0x18;
}
pub const CHARSET_UTF8: u8 = 0x01;
pub const INDICATOR_READ_STATUS: u8 = 0x00;
pub const INDICATOR_DELETED_STATUS: u8 = 0x01;
#[must_use]
pub const fn push_message_params() -> Bytes {
Bytes::from_static(b"\x14\x01\x01")
}
#[must_use]
pub const fn get_message_params() -> Bytes {
Bytes::from_static(b"\x14\x01\x01")
}
#[must_use]
pub fn set_message_status_params(indicator: u8, value: u8) -> Vec<u8> {
let mut params = Vec::with_capacity(6);
params.extend_from_slice(&[tag::STATUS_INDICATOR, 0x01, indicator]);
params.extend_from_slice(&[tag::STATUS_VALUE, 0x01, value]);
params
}
#[must_use]
pub const fn set_notification_registration_params(enable: bool) -> Bytes {
if enable {
Bytes::from_static(b"\x0e\x01\x01\x0f\x01\x00")
} else {
Bytes::from_static(b"\x0e\x01\x00\x0f\x01\x00")
}
}
pub fn list_messages_params(
max_count: u16,
offset: u16,
read_status: Option<u8>,
originating_address: Option<&str>,
period_begin: Option<&str>,
period_end: Option<&str>,
) -> Result<Vec<u8>, MapError> {
let mut cap = 8_usize;
if read_status.is_some() {
cap = cap.saturating_add(3);
}
for s in [originating_address, period_begin, period_end].into_iter().flatten() {
cap = cap.saturating_add(s.len().saturating_add(3));
}
let mut params = Vec::with_capacity(cap);
params.extend_from_slice(&[tag::MAX_LIST_COUNT, 0x02]);
params.extend_from_slice(&max_count.to_be_bytes());
params.extend_from_slice(&[tag::LIST_START_OFFSET, 0x02]);
params.extend_from_slice(&offset.to_be_bytes());
if let Some(status) = read_status {
params.extend_from_slice(&[tag::FILTER_READ_STATUS, 0x01, status]);
}
push_str_tlv(&mut params, tag::FILTER_ORIGINATOR, originating_address)?;
push_str_tlv(&mut params, tag::FILTER_PERIOD_BEGIN, period_begin)?;
push_str_tlv(&mut params, tag::FILTER_PERIOD_END, period_end)?;
Ok(params)
}
fn push_str_tlv(out: &mut Vec<u8>, tag_byte: u8, value: Option<&str>) -> Result<(), MapError> {
let Some(s) = value else {
return Ok(());
};
if s.contains('\x00') {
return Err(MapError::InvalidInput("filter string must not contain null bytes"));
}
let len = u8::try_from(s.len().saturating_add(1))
.map_err(|_| MapError::InvalidInput("filter string exceeds 254 UTF-8 bytes"))?;
out.push(tag_byte);
out.push(len);
out.extend_from_slice(s.as_bytes());
out.push(0x00);
Ok(())
}
#[cfg(test)]
mod tests {
use super::list_messages_params;
use crate::MapError;
#[test]
fn list_messages_params_with_originator() -> Result<(), MapError> {
let bytes = list_messages_params(1024, 0, None, Some("5550001001"), None, None)?;
let tlv: &[u8] = b"\x08\x0b5550001001\x00";
assert!(bytes.windows(tlv.len()).any(|w| w == tlv));
Ok(())
}
#[test]
fn list_messages_params_with_period_filters() -> Result<(), MapError> {
let bytes = list_messages_params(
1024,
0,
None,
None,
Some("20260601T000000"),
Some("20260605T235959"),
)?;
let begin_tlv: &[u8] = b"\x04\x1020260601T000000\x00";
let end_tlv: &[u8] = b"\x05\x1020260605T235959\x00";
assert!(bytes.windows(begin_tlv.len()).any(|w| w == begin_tlv));
assert!(bytes.windows(end_tlv.len()).any(|w| w == end_tlv));
Ok(())
}
#[test]
fn list_messages_params_rejects_null_byte() {
let result = list_messages_params(1024, 0, None, Some("abc\x00def"), None, None);
assert!(matches!(result, Err(MapError::InvalidInput(_))));
}
#[test]
fn list_messages_params_rejects_overlong() {
let long: String = "a".repeat(255);
let result = list_messages_params(1024, 0, None, Some(&long), None, None);
assert!(matches!(result, Err(MapError::InvalidInput(_))));
}
}