bt_obex/
profile.rs
1use bt_rfcomm::profile::{rfcomm_connect_parameters, server_channel_from_protocol};
6use bt_rfcomm::ServerChannel;
7use fuchsia_bluetooth::profile::{
8 l2cap_connect_parameters, psm_from_protocol, Attribute, DataElement, ProtocolDescriptor, Psm,
9};
10use fuchsia_bluetooth::types::PeerId;
11use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
12
13use crate::client::ObexClient;
14use crate::error::Error;
15use crate::transport::TransportType;
16
17impl From<bredr::ConnectParameters> for TransportType {
18 fn from(src: bredr::ConnectParameters) -> TransportType {
19 match src {
20 bredr::ConnectParameters::L2cap(_) => TransportType::L2cap,
21 _ => TransportType::Rfcomm,
22 }
23 }
24}
25
26pub const GOEP_L2CAP_PSM_ATTRIBUTE: u16 = 0x0200;
29
30pub fn is_obex_protocol(protocol: &Vec<ProtocolDescriptor>) -> bool {
35 protocol.iter().any(|descriptor| descriptor.protocol == bredr::ProtocolIdentifier::Obex)
36}
37
38pub fn obex_protocol_l2cap(psm: Psm) -> Vec<ProtocolDescriptor> {
40 vec![
41 ProtocolDescriptor {
42 protocol: bredr::ProtocolIdentifier::L2Cap,
43 params: vec![DataElement::Uint16(psm.into())],
44 },
45 ProtocolDescriptor { protocol: bredr::ProtocolIdentifier::Obex, params: vec![] },
46 ]
47}
48
49pub fn obex_protocol_rfcomm(channel: ServerChannel) -> Vec<ProtocolDescriptor> {
51 vec![
52 ProtocolDescriptor { protocol: bredr::ProtocolIdentifier::L2Cap, params: vec![] },
53 ProtocolDescriptor {
54 protocol: bredr::ProtocolIdentifier::Rfcomm,
55 params: vec![DataElement::Uint8(channel.into())],
56 },
57 ProtocolDescriptor { protocol: bredr::ProtocolIdentifier::Obex, params: vec![] },
58 ]
59}
60
61pub fn goep_l2cap_psm_attribute(psm: Psm) -> Attribute {
63 Attribute { id: GOEP_L2CAP_PSM_ATTRIBUTE, element: DataElement::Uint16(psm.into()) }
64}
65
66fn parse_goep_l2cap_psm_attribute(attribute: &Attribute) -> Option<Psm> {
69 if attribute.id != GOEP_L2CAP_PSM_ATTRIBUTE {
70 return None;
71 }
72
73 if let DataElement::Uint16(psm) = attribute.element {
74 Some(Psm::new(psm))
75 } else {
76 None
77 }
78}
79
80pub fn parse_obex_search_result(
84 protocol: &Vec<ProtocolDescriptor>,
85 attributes: &Vec<Attribute>,
86) -> Option<bredr::ConnectParameters> {
87 if !is_obex_protocol(protocol) {
88 return None;
89 }
90
91 if let Some(l2cap_psm) = attributes.iter().find_map(parse_goep_l2cap_psm_attribute) {
94 return Some(l2cap_connect_parameters(
95 l2cap_psm,
96 fidl_bt::ChannelMode::EnhancedRetransmission,
97 ));
98 }
99
100 if let Some(psm) = psm_from_protocol(protocol) {
103 return Some(l2cap_connect_parameters(psm, fidl_bt::ChannelMode::EnhancedRetransmission));
104 }
105
106 server_channel_from_protocol(protocol).map(|sc| rfcomm_connect_parameters(sc))
108}
109
110pub async fn connect_to_obex_service(
114 id: PeerId,
115 profile: &bredr::ProfileProxy,
116 parameters: bredr::ConnectParameters,
117) -> Result<ObexClient, Error> {
118 let channel = profile
119 .connect(&id.into(), ¶meters)
120 .await
121 .map_err(anyhow::Error::from)?
122 .map_err(|e| anyhow::format_err!("{e:?}"))?;
123 let local = channel.try_into()?;
124 let transport_type = parameters.into();
125 Ok(ObexClient::new(local, transport_type))
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 use assert_matches::assert_matches;
133
134 #[test]
135 fn parse_invalid_goep_attribute_is_none() {
136 let attribute = Attribute { id: GOEP_L2CAP_PSM_ATTRIBUTE, element: DataElement::Uint8(5) };
138 assert_matches!(parse_goep_l2cap_psm_attribute(&attribute), None);
139
140 let attribute = Attribute {
142 id: 0x3333, element: DataElement::Uint16(5),
144 };
145 assert_matches!(parse_goep_l2cap_psm_attribute(&attribute), None);
146 }
147
148 #[test]
149 fn parse_goep_attribute_success() {
150 let attribute =
151 Attribute { id: GOEP_L2CAP_PSM_ATTRIBUTE, element: DataElement::Uint16(45) };
152 assert_eq!(parse_goep_l2cap_psm_attribute(&attribute), Some(Psm::new(45)));
153 }
154
155 #[test]
156 fn parse_invalid_search_result_is_none() {
157 let protocol =
159 vec![ProtocolDescriptor { protocol: bredr::ProtocolIdentifier::Obex, params: vec![] }];
160 assert_matches!(parse_obex_search_result(&protocol, &vec![]), None);
161 }
162
163 #[test]
164 fn parse_non_obex_search_result_is_none() {
165 let protocol = vec![ProtocolDescriptor {
167 protocol: bredr::ProtocolIdentifier::L2Cap,
168 params: vec![DataElement::Uint16(27)],
169 }];
170 let attributes = vec![goep_l2cap_psm_attribute(Psm::new(55))];
171 assert_matches!(parse_obex_search_result(&protocol, &attributes), None);
174 }
175
176 #[test]
177 fn parse_obex_search_result_with_l2cap() {
178 let l2cap_protocol = obex_protocol_l2cap(Psm::new(59));
179 let expected = bredr::ConnectParameters::L2cap(bredr::L2capParameters {
180 psm: Some(59),
181 parameters: Some(fidl_bt::ChannelParameters {
182 channel_mode: Some(fidl_bt::ChannelMode::EnhancedRetransmission),
183 ..fidl_bt::ChannelParameters::default()
184 }),
185 ..bredr::L2capParameters::default()
186 });
187 let result =
188 parse_obex_search_result(&l2cap_protocol, &vec![]).expect("valid search result");
189 assert_eq!(result, expected);
190 }
191
192 #[test]
193 fn parse_obex_search_result_with_rfcomm() {
194 let server_channel = 8.try_into().unwrap();
195 let rfcomm_protocol = obex_protocol_rfcomm(server_channel);
196 let expected = bredr::ConnectParameters::Rfcomm(bredr::RfcommParameters {
197 channel: Some(8),
198 ..bredr::RfcommParameters::default()
199 });
200 let result =
201 parse_obex_search_result(&rfcomm_protocol, &vec![]).expect("valid search result");
202 assert_eq!(result, expected);
203 }
204
205 #[test]
206 fn parse_obex_search_result_with_l2cap_and_rfcomm() {
207 let server_channel = 7.try_into().unwrap();
208 let attributes = vec![
209 Attribute {
210 id: 0x33, element: DataElement::Uint8(5),
212 },
213 goep_l2cap_psm_attribute(Psm::new(55)),
214 ];
215 let expected = bredr::ConnectParameters::L2cap(bredr::L2capParameters {
217 psm: Some(55),
218 parameters: Some(fidl_bt::ChannelParameters {
219 channel_mode: Some(fidl_bt::ChannelMode::EnhancedRetransmission),
220 ..fidl_bt::ChannelParameters::default()
221 }),
222 ..bredr::L2capParameters::default()
223 });
224 let result = parse_obex_search_result(&obex_protocol_rfcomm(server_channel), &attributes)
225 .expect("valid search result");
226 assert_eq!(result, expected);
227 }
228}