bt_map/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use bitflags::bitflags;
use bt_obex::ObexError;
use fidl_fuchsia_bluetooth_map as fidl_bt_map;
use objects::ObexObjectError;
use packet_encoding::{codable_as_bitmask, decodable_enum};
use std::fmt;
use std::str::FromStr;

pub mod packets;

use thiserror::Error;

// Tag IDs are listed in MAP v1.4.2 Section 6.3.1.
pub const NOTIFICATION_STATUS_TAG_ID: u8 = 0x0E;
pub const MAP_SUPPORTED_FEATURES_TAG_ID: u8 = 0x29;

/// Errors that occur during the use of the MAP library.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Error {
    #[error("Obex error: {:?}", .0)]
    Obex(#[from] ObexError),

    #[error("Invalid message type")]
    InvalidMessageType,

    #[error("Service record item is missing or invalid: {:?}", .0)]
    InvalidSdp(ServiceRecordItem),

    #[error("Service is not GOEP interoperable")]
    NotGoepInteroperable,

    #[error("Invalid parameters")]
    InvalidParameters,

    #[error("MAS instance does not exist: (id {:?})", .0)]
    MasInstanceDoesNotExist(u8),

    #[error("Feature is not supported by the remote peer")]
    NotSupported,

    #[error("Invalid Message Notification Service state")]
    InvalidMnsState,

    #[error("Not connected to any Message Access Service instances")]
    MasUnavailable,

    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

impl Error {
    pub fn other(error_msg: impl std::fmt::Debug) -> Self {
        Error::Other(anyhow::format_err!("{error_msg:?}"))
    }
}

impl From<&Error> for fidl_bt_map::Error {
    fn from(value: &Error) -> Self {
        match value {
            &Error::Obex(_) => fidl_bt_map::Error::Unavailable,
            &Error::InvalidMessageType => fidl_bt_map::Error::Unknown,
            &Error::InvalidSdp(_) => fidl_bt_map::Error::Unavailable,
            &Error::NotGoepInteroperable => fidl_bt_map::Error::Unavailable,
            &Error::InvalidParameters => fidl_bt_map::Error::BadRequest,
            &Error::MasInstanceDoesNotExist(_) => fidl_bt_map::Error::NotFound,
            &Error::NotSupported => fidl_bt_map::Error::NotSupported,
            &Error::InvalidMnsState => fidl_bt_map::Error::BadRequest,
            &Error::MasUnavailable => fidl_bt_map::Error::Unavailable,
            &Error::Other(_) => fidl_bt_map::Error::Unknown,
        }
    }
}

impl From<Error> for fidl_bt_map::Error {
    fn from(value: Error) -> Self {
        (&value).into()
    }
}

/// Service record item expected from MAP related SDP.
#[derive(Debug)]
pub enum ServiceRecordItem {
    MasServiceClassId,
    ObexProtocolDescriptor,
    MapProfileDescriptor,
    MasInstanceId,
    SupportedMessageTypes,
    MapSupportedFeatures,
    ServiceName,
}

bitflags! {
    /// See MAP v1.4.2 section 7.1 SDP Interoperability Requirements.
    /// According to MAP v1.4.2 section 6.3.1, the features are
    /// represented in big-endian byte ordering.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct MapSupportedFeatures: u32 {
        const NOTIFICATION_REGISTRATION                     = 0x00000001;
        const NOTIFICATION                                  = 0x00000002;
        const BROWSING                                      = 0x00000004;
        const UPLOADING                                     = 0x00000008;
        const DELETE                                        = 0x00000010;
        const INSTANCE_INFORMATION                          = 0x00000020;
        const EXTENDED_EVENT_REPORT_1_1                     = 0x00000040;
        const EVENT_REPORT_VERSION_1_2                      = 0x00000080;
        const MESSAGE_FORMAT_VERSION_1_1                    = 0x00000100;
        const MESSAGES_LISTING_FORMAT_VERSION_1_1           = 0x00000200;
        const PERSISTENT_MESSAGE_HANDLES                    = 0x00000400;
        const DATABASE_IDENTIFIER                           = 0x00000800;
        const FOLDER_VERSION_COUNTER                        = 0x00001000;
        const CONVERSATION_VERSION_COUNTER                  = 0x00002000;
        const PARTICIPANT_PRESENCE_CHANGE_NOTIFICATION      = 0x00004000;
        const PARTICIPANT_CHAT_STATE_CHANGE_NOTIFICATION    = 0x00008000;
        const PBAP_CONTACT_CROSS_REFERENCE                  = 0x00010000;
        const NOTIFICATION_FILTERING                        = 0x00020000;
        const UTC_OFFSET_TIMESTAMP_FORMAT                   = 0x00040000;
        // Below feature bits are only available for Message Access
        // Services and not for Message Notification Services.
        const MAPSUPPORTEDFEATURES_IN_CONNECT_REQUEST       = 0x00080000;
        const CONVERSATION_LISTING                          = 0x00100000;
        const OWNER_STATUS                                  = 0x00200000;
        const MESSAGE_FORWARDING                            = 0x00400000;

        const SUPPORTS_NOTIFICATION = Self::NOTIFICATION.bits() | Self::NOTIFICATION_REGISTRATION.bits();
    }
}

decodable_enum! {
    pub enum MessageType<u8, Error, InvalidMessageType> {
        Email = 0x1,
        SmsGsm = 0x2,
        SmsCdma = 0x4,
        Mms = 0x8,
        Im = 0x10,
        // Bits 5-7 are RFU.
    }
}

impl MessageType {
    const MESSAGE_TYPE_EMAIL: &'static str = "EMAIL";
    const MESSAGE_TYPE_SMS_GSM: &'static str = "SMS_GSM";
    const MESSAGE_TYPE_SMS_CDMA: &'static str = "SMS_CDMA";
    const MESSAGE_TYPE_MMS: &'static str = "MMS";
    const MESSAGE_TYPE_IM: &'static str = "IM";
}

impl fmt::Display for MessageType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let val = match self {
            Self::Email => Self::MESSAGE_TYPE_EMAIL,
            Self::SmsGsm => Self::MESSAGE_TYPE_SMS_GSM,
            Self::SmsCdma => Self::MESSAGE_TYPE_SMS_CDMA,
            Self::Mms => Self::MESSAGE_TYPE_MMS,
            Self::Im => Self::MESSAGE_TYPE_IM,
        };
        write!(f, "{}", val)
    }
}

impl FromStr for MessageType {
    type Err = ObexObjectError;
    fn from_str(src: &str) -> Result<Self, Self::Err> {
        match src {
            Self::MESSAGE_TYPE_EMAIL => Ok(Self::Email),
            Self::MESSAGE_TYPE_SMS_GSM => Ok(Self::SmsGsm),
            Self::MESSAGE_TYPE_SMS_CDMA => Ok(Self::SmsCdma),
            Self::MESSAGE_TYPE_MMS => Ok(Self::Mms),
            Self::MESSAGE_TYPE_IM => Ok(Self::Im),
            v => Err(ObexObjectError::invalid_data(v)),
        }
    }
}

impl From<MessageType> for fidl_bt_map::MessageType {
    fn from(value: MessageType) -> Self {
        match value {
            MessageType::Email => fidl_bt_map::MessageType::EMAIL,
            MessageType::SmsGsm => fidl_bt_map::MessageType::SMS_GSM,
            MessageType::SmsCdma => fidl_bt_map::MessageType::SMS_CDMA,
            MessageType::Mms => fidl_bt_map::MessageType::MMS,
            MessageType::Im => fidl_bt_map::MessageType::IM,
        }
    }
}

codable_as_bitmask!(MessageType, u8, Error, InvalidMessageType);

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    #[test]
    fn message_type() {
        let email_and_im = 0x11;

        let types_: HashSet<MessageType> = MessageType::from_bits(email_and_im)
            .collect::<Result<HashSet<_>, _>>()
            .expect("should not fail");

        assert_eq!(2, types_.len());

        let expected = [MessageType::Email, MessageType::Im].into_iter().collect();

        assert_eq!(types_, expected);

        let all = MessageType::VARIANTS;
        let value = MessageType::to_bits(all.iter()).expect("should work");
        assert_eq!(0x1F, value);
    }

    #[test]
    fn map_supported_features() {
        const NOTIFICATION_REG: u32 = 0x1;
        assert_eq!(
            MapSupportedFeatures::from_bits_truncate(NOTIFICATION_REG),
            MapSupportedFeatures::NOTIFICATION_REGISTRATION
        );

        const NOTIFICATION_REG_AND_OWNER_STATUS: u32 = 0x200001;
        let features = MapSupportedFeatures::from_bits_truncate(NOTIFICATION_REG_AND_OWNER_STATUS);
        assert!(features.contains(MapSupportedFeatures::NOTIFICATION_REGISTRATION));
        assert!(features.contains(MapSupportedFeatures::OWNER_STATUS));
        assert_eq!(
            features,
            MapSupportedFeatures::NOTIFICATION_REGISTRATION | MapSupportedFeatures::OWNER_STATUS
        );
    }

    #[test]
    fn supports_notification() {
        // Has notification (bit 0) and registration (bit 1).
        let features = MapSupportedFeatures::from_bits_truncate(0x3);
        assert!(features.contains(MapSupportedFeatures::SUPPORTS_NOTIFICATION));

        let features = MapSupportedFeatures::NOTIFICATION;
        assert!(!features.contains(MapSupportedFeatures::SUPPORTS_NOTIFICATION));

        let features = MapSupportedFeatures::NOTIFICATION_REGISTRATION;
        assert!(!features.contains(MapSupportedFeatures::SUPPORTS_NOTIFICATION));

        let features = MapSupportedFeatures::MESSAGE_FORWARDING;
        assert!(!features.contains(MapSupportedFeatures::SUPPORTS_NOTIFICATION));
    }
}