sms_client/
types.rs

1//! Generic types that apply to both HTTP and Websocket interfaces.
2
3use serde::{Deserialize, Serialize};
4
5/// Represents a stored SMS message from the database.
6#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
7pub struct SmsStoredMessage {
8    /// Unique identifier for the message.
9    pub message_id: i64,
10
11    /// The phone number associated with this message.
12    pub phone_number: String,
13
14    /// The actual text content of the message.
15    pub message_content: String,
16
17    /// Optional reference number for message tracking.
18    /// This is assigned by the modem and is only present for outgoing messages.
19    pub message_reference: Option<u8>,
20
21    /// Whether this message was sent (true) or received (false).
22    pub is_outgoing: bool,
23
24    /// Current status of the message (e.g., "sent", "delivered", "failed").
25    pub status: String,
26
27    /// Unix timestamp when the message was created.
28    pub created_at: Option<u32>,
29
30    /// Optional Unix timestamp when the message was completed/delivered.
31    pub completed_at: Option<u32>,
32}
33
34/// A partial message delivery report, as it comes from the modem.
35#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
36pub struct SmsPartialDeliveryReport {
37    /// The target phone number that received the message (and has now sent back a delivery report).
38    phone_number: String,
39
40    /// The modem assigned message reference, this is basically useless outside short-term tracking
41    /// the `message_id` is unique should always be used instead for identification.
42    reference_id: u8,
43
44    /// The SMS TP-Status: <https://www.etsi.org/deliver/etsi_ts/123000_123099/123040/16.00.00_60/ts_123040v160000p.pdf#page=71>
45    status: u8,
46}
47
48/// <https://www.etsi.org/deliver/etsi_ts/123000_123099/123040/16.00.00_60/ts_123040v160000p.pdf#page=71>
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[repr(u8)]
51pub enum SmsDeliveryReportStatus {
52    // Short message transaction completed (0x00-0x1F)
53    /// Short message received by the SME successfully
54    ReceivedBySme = 0x00,
55    /// Short message forwarded by the SC to the SME but delivery confirmation unavailable
56    ForwardedButUnconfirmed = 0x01,
57    /// Short message replaced by the SC
58    ReplacedBySc = 0x02,
59    // 0x03-0x0F Reserved
60    // 0x10-0x1F SC specific values
61
62    // Temporary error, SC still trying (0x20-0x3F)
63    /// Network congestion preventing delivery, SC will retry
64    Congestion = 0x20,
65    /// SME is busy, SC will retry delivery
66    SmeBusy = 0x21,
67    /// No response from SME, SC will retry delivery
68    NoResponseFromSme = 0x22,
69    /// Service rejected by network, SC will retry delivery
70    ServiceRejected = 0x23,
71    /// Quality of service not available, SC will retry delivery
72    QualityOfServiceNotAvailable = 0x24,
73    /// Error in SME, SC will retry delivery
74    ErrorInSme = 0x25,
75    // 0x26-0x2F Reserved
76    // 0x30-0x3F SC specific values
77
78    // Permanent error, SC not making more attempts (0x40-0x5F)
79    /// Remote procedure error - permanent failure
80    RemoteProcedureError = 0x40,
81    /// Incompatible destination - permanent failure
82    IncompatibleDestination = 0x41,
83    /// Connection rejected by SME - permanent failure
84    ConnectionRejectedBySme = 0x42,
85    /// Destination not obtainable - permanent failure
86    NotObtainable = 0x43,
87    /// Quality of service not available - permanent failure
88    QualityOfServiceNotAvailablePermanent = 0x44,
89    /// No interworking available - permanent failure
90    NoInterworkingAvailable = 0x45,
91    /// SM validity period expired - permanent failure
92    SmValidityPeriodExpired = 0x46,
93    /// SM deleted by originating SME - permanent failure
94    SmDeletedByOriginatingSme = 0x47,
95    /// SM deleted by SC administration - permanent failure
96    SmDeletedByScAdministration = 0x48,
97    /// SM does not exist in SC - permanent failure
98    SmDoesNotExist = 0x49,
99    // 0x4A-0x4F Reserved
100    // 0x50-0x5F SC specific values
101
102    // Temporary error, SC not making more attempts (0x60-0x7F)
103    /// Network congestion, SC has stopped retry attempts
104    CongestionNoRetry = 0x60,
105    /// SME busy, SC has stopped retry attempts
106    SmeBusyNoRetry = 0x61,
107    /// No response from SME, SC has stopped retry attempts
108    NoResponseFromSmeNoRetry = 0x62,
109    /// Service rejected, SC has stopped retry attempts
110    ServiceRejectedNoRetry = 0x63,
111    /// Quality of service not available, SC has stopped retry attempts
112    QualityOfServiceNotAvailableNoRetry = 0x64,
113    /// Error in SME, SC has stopped retry attempts
114    ErrorInSmeNoRetry = 0x65,
115    // 0x66-0x69 Reserved
116    // 0x6A-0x6F Reserved
117    // 0x70-0x7F SC specific values
118    /// Unknown or reserved status code - treated as service rejected per spec
119    Unknown(u8),
120}
121impl From<u8> for SmsDeliveryReportStatus {
122    fn from(value: u8) -> Self {
123        use SmsDeliveryReportStatus::{
124            Congestion, CongestionNoRetry, ConnectionRejectedBySme, ErrorInSme, ErrorInSmeNoRetry,
125            ForwardedButUnconfirmed, IncompatibleDestination, NoInterworkingAvailable,
126            NoResponseFromSme, NoResponseFromSmeNoRetry, NotObtainable,
127            QualityOfServiceNotAvailable, QualityOfServiceNotAvailableNoRetry,
128            QualityOfServiceNotAvailablePermanent, ReceivedBySme, RemoteProcedureError,
129            ReplacedBySc, ServiceRejected, ServiceRejectedNoRetry, SmDeletedByOriginatingSme,
130            SmDeletedByScAdministration, SmDoesNotExist, SmValidityPeriodExpired, SmeBusy,
131            SmeBusyNoRetry, Unknown,
132        };
133
134        match value {
135            // Transaction completed successfully
136            0x00 => ReceivedBySme,
137            0x01 => ForwardedButUnconfirmed,
138            0x02 => ReplacedBySc,
139
140            // Temporary errors, SC still trying
141            0x20 => Congestion,
142            0x21 => SmeBusy,
143            0x22 => NoResponseFromSme,
144            0x23 => ServiceRejected,
145            0x24 => QualityOfServiceNotAvailable,
146            0x25 => ErrorInSme,
147
148            // Permanent errors
149            0x40 => RemoteProcedureError,
150            0x41 => IncompatibleDestination,
151            0x42 => ConnectionRejectedBySme,
152            0x43 => NotObtainable,
153            0x44 => QualityOfServiceNotAvailablePermanent,
154            0x45 => NoInterworkingAvailable,
155            0x46 => SmValidityPeriodExpired,
156            0x47 => SmDeletedByOriginatingSme,
157            0x48 => SmDeletedByScAdministration,
158            0x49 => SmDoesNotExist,
159
160            // Temporary errors, SC not retrying
161            0x60 => CongestionNoRetry,
162            0x61 => SmeBusyNoRetry,
163            0x62 => NoResponseFromSmeNoRetry,
164            0x63 => ServiceRejectedNoRetry,
165            0x64 => QualityOfServiceNotAvailableNoRetry,
166            0x65 => ErrorInSmeNoRetry,
167
168            // All other values (reserved, SC-specific, or unknown)
169            _ => Unknown(value),
170        }
171    }
172}
173impl SmsDeliveryReportStatus {
174    /// Returns true if the SMS was successfully delivered to the SME
175    #[must_use]
176    pub fn is_successful(&self) -> bool {
177        matches!(
178            self,
179            Self::ReceivedBySme | Self::ForwardedButUnconfirmed | Self::ReplacedBySc
180        )
181    }
182
183    /// Returns true if this is a temporary error where SC is still trying
184    #[must_use]
185    pub fn is_temporary_retrying(&self) -> bool {
186        use SmsDeliveryReportStatus::{
187            Congestion, ErrorInSme, NoResponseFromSme, QualityOfServiceNotAvailable,
188            ServiceRejected, SmeBusy, Unknown,
189        };
190
191        matches!(
192            self,
193            Congestion
194                | SmeBusy
195                | NoResponseFromSme
196                | ServiceRejected
197                | QualityOfServiceNotAvailable
198                | ErrorInSme
199        ) || matches!(self, Unknown(val) if *val >= 0x20 && *val <= 0x3F)
200    }
201
202    /// Returns true if this is a permanent error (no more delivery attempts)
203    #[must_use]
204    pub fn is_permanent_error(&self) -> bool {
205        use SmsDeliveryReportStatus::{
206            ConnectionRejectedBySme, IncompatibleDestination, NoInterworkingAvailable,
207            NotObtainable, QualityOfServiceNotAvailablePermanent, RemoteProcedureError,
208            SmDeletedByOriginatingSme, SmDeletedByScAdministration, SmDoesNotExist,
209            SmValidityPeriodExpired, Unknown,
210        };
211
212        matches!(
213            self,
214            RemoteProcedureError
215                | IncompatibleDestination
216                | ConnectionRejectedBySme
217                | NotObtainable
218                | QualityOfServiceNotAvailablePermanent
219                | NoInterworkingAvailable
220                | SmValidityPeriodExpired
221                | SmDeletedByOriginatingSme
222                | SmDeletedByScAdministration
223                | SmDoesNotExist
224        ) || matches!(self, Unknown(val) if *val >= 0x40 && *val <= 0x5F)
225    }
226
227    /// Returns true if this is a temporary error where SC has stopped trying
228    #[must_use]
229    pub fn is_temporary_no_retry(&self) -> bool {
230        use SmsDeliveryReportStatus::{
231            CongestionNoRetry, ErrorInSmeNoRetry, NoResponseFromSmeNoRetry,
232            QualityOfServiceNotAvailableNoRetry, ServiceRejectedNoRetry, SmeBusyNoRetry, Unknown,
233        };
234
235        matches!(
236            self,
237            CongestionNoRetry
238                | SmeBusyNoRetry
239                | NoResponseFromSmeNoRetry
240                | ServiceRejectedNoRetry
241                | QualityOfServiceNotAvailableNoRetry
242                | ErrorInSmeNoRetry
243        ) || matches!(self, Unknown(val) if *val >= 0x60 && *val <= 0x7F)
244    }
245
246    /// Converts the status to a simplified status group for easier categorization
247    #[must_use]
248    pub fn to_status_group(&self) -> SmsDeliveryReportStatusGroup {
249        if self.is_successful() {
250            SmsDeliveryReportStatusGroup::Received
251        } else if self.is_temporary_retrying() {
252            SmsDeliveryReportStatusGroup::Sent
253        } else if self.is_permanent_error() || self.is_temporary_no_retry() {
254            // Both permanent errors and temporary errors with no retry are treated as failures
255            if self.is_permanent_error() {
256                SmsDeliveryReportStatusGroup::PermanentFailure
257            } else {
258                SmsDeliveryReportStatusGroup::TemporaryFailure
259            }
260        } else {
261            // For unknown status codes, classify based on their range.
262            match self {
263                Self::Unknown(val) if *val >= 0x20 && *val <= 0x3F => {
264                    SmsDeliveryReportStatusGroup::Sent
265                }
266                Self::Unknown(val) if *val >= 0x40 && *val <= 0x5F => {
267                    SmsDeliveryReportStatusGroup::PermanentFailure
268                }
269                Self::Unknown(val) if *val >= 0x60 && *val <= 0x7F => {
270                    SmsDeliveryReportStatusGroup::TemporaryFailure
271                }
272                _ => SmsDeliveryReportStatusGroup::PermanentFailure, // Default for truly unknown codes
273            }
274        }
275    }
276}
277
278/// Generalised group for message delivery status.
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum SmsDeliveryReportStatusGroup {
281    /// Message was sent but delivery is still pending (temporary errors with retry)
282    Sent,
283    /// Message was successfully received by the destination.
284    Received,
285    /// Temporary delivery failure where SC has stopped retrying.
286    TemporaryFailure,
287    /// Permanent delivery failure - message will not be delivered.
288    PermanentFailure,
289}
290impl From<SmsDeliveryReportStatus> for SmsDeliveryReportStatusGroup {
291    fn from(status: SmsDeliveryReportStatus) -> Self {
292        status.to_status_group()
293    }
294}
295
296/// Represents the current status of the modem.
297#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
298pub enum ModemStatusUpdateState {
299    /// Modem is starting up.
300    Startup,
301
302    /// Modem is online and operational.
303    Online,
304
305    /// Modem is shutting down.
306    ShuttingDown,
307
308    /// Modem is offline and not operational.
309    Offline,
310}
311impl std::fmt::Display for ModemStatusUpdateState {
312    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313        match self {
314            ModemStatusUpdateState::Startup => write!(f, "Startup"),
315            ModemStatusUpdateState::Online => write!(f, "Online"),
316            ModemStatusUpdateState::ShuttingDown => write!(f, "ShuttingDown"),
317            ModemStatusUpdateState::Offline => write!(f, "Offline"),
318        }
319    }
320}
321
322/// GNSS (Global Navigation Satellite System) fix status.
323#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
324pub enum GnssFixStatus {
325    /// GNSS fix status is unknown.
326    Unknown,
327
328    /// No GNSS fix.
329    NotFix,
330
331    /// 2D GNSS fix (latitude and longitude only).
332    Fix2D,
333
334    /// 3D GNSS fix (latitude, longitude, and altitude).
335    Fix3D,
336}
337
338/// Represents a GNSS position report with optional fields for satellite info.
339#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
340pub struct GnssPositionReport {
341    /// Indicates whether the GNSS receiver is currently running.
342    pub run_status: bool,
343
344    /// Whether a valid fix has been obtained.
345    pub fix_status: bool,
346
347    /// UTC time of the position report in ISO 8601 format.
348    pub utc_time: String,
349
350    /// Latitude in decimal degrees.
351    pub latitude: Option<f64>,
352
353    /// Longitude in decimal degrees.
354    pub longitude: Option<f64>,
355
356    /// Mean sea level altitude in meters.
357    pub msl_altitude: Option<f64>,
358
359    /// Ground speed in meters per second.
360    pub ground_speed: Option<f32>,
361
362    /// Ground course in degrees.
363    pub ground_course: Option<f32>,
364
365    /// Fix mode indicating 2D/3D fix or unknown.
366    pub fix_mode: GnssFixStatus,
367
368    /// Horizontal Dilution of Precision.
369    pub hdop: Option<f32>,
370
371    /// Position Dilution of Precision.
372    pub pdop: Option<f32>,
373
374    /// Vertical Dilution of Precision.
375    pub vdop: Option<f32>,
376
377    /// Number of GPS satellites in view.
378    pub gps_in_view: Option<u8>,
379
380    /// Number of GNSS satellites used in the fix.
381    pub gnss_used: Option<u8>,
382
383    /// Number of GLONASS satellites in view.
384    pub glonass_in_view: Option<u8>,
385}