fuchsia_bluetooth/profile/
avrcp.rs1use crate::profile::{Psm, elem_to_profile_descriptor, psm_from_protocol};
6use crate::types::Uuid;
7use anyhow::{Error, format_err};
8use bitflags::bitflags;
9use fidl_fuchsia_bluetooth_bredr::*;
10use log::info;
11
12bitflags! {
13 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
16 pub struct AvrcpTargetFeatures: u16 {
17 const CATEGORY1 = 1 << 0;
18 const CATEGORY2 = 1 << 1;
19 const CATEGORY3 = 1 << 2;
20 const CATEGORY4 = 1 << 3;
21 const PLAYERSETTINGS = 1 << 4;
22 const GROUPNAVIGATION = 1 << 5;
23 const SUPPORTSBROWSING = 1 << 6;
24 const SUPPORTSMULTIPLEMEDIAPLAYERS = 1 << 7;
25 const SUPPORTSCOVERART = 1 << 8;
26 }
28}
29
30bitflags! {
31 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34 pub struct AvrcpControllerFeatures: u16 {
35 const CATEGORY1 = 1 << 0;
36 const CATEGORY2 = 1 << 1;
37 const CATEGORY3 = 1 << 2;
38 const CATEGORY4 = 1 << 3;
39 const SUPPORTSBROWSING = 1 << 6;
41 const SUPPORTSCOVERARTGETIMAGEPROPERTIES = 1 << 7;
42 const SUPPORTSCOVERARTGETIMAGE = 1 << 8;
43 const SUPPORTSCOVERARTGETLINKEDTHUMBNAIL = 1 << 9;
44 }
46}
47
48impl AvrcpControllerFeatures {
49 pub fn supports_cover_art(&self) -> bool {
51 self.contains(
52 AvrcpControllerFeatures::SUPPORTSCOVERARTGETIMAGE
53 | AvrcpControllerFeatures::SUPPORTSCOVERARTGETIMAGEPROPERTIES
54 | AvrcpControllerFeatures::SUPPORTSCOVERARTGETLINKEDTHUMBNAIL,
55 )
56 }
57}
58
59pub const SDP_SUPPORTED_FEATURES: u16 = 0x0311;
60
61pub const AV_REMOTE_TARGET_CLASS: u16 = 0x110c;
62pub const AV_REMOTE_CLASS: u16 = 0x110e;
63pub const AV_REMOTE_CONTROLLER_CLASS: u16 = 0x110f;
64
65#[derive(PartialEq, Hash, Clone, Copy)]
67pub struct AvrcpProtocolVersion(pub u8, pub u8);
68
69impl std::fmt::Debug for AvrcpProtocolVersion {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "{}.{}", self.0, self.1)
72 }
73}
74
75#[derive(Debug, PartialEq, Clone, Copy)]
77pub enum AvrcpService {
78 Target {
79 features: AvrcpTargetFeatures,
80 psm: Psm,
81 protocol_version: AvrcpProtocolVersion,
82 },
83 Controller {
84 features: AvrcpControllerFeatures,
85 psm: Psm,
86 protocol_version: AvrcpProtocolVersion,
87 },
88}
89
90impl AvrcpService {
91 pub fn supports_browsing(&self) -> bool {
93 match &self {
94 Self::Target { features, .. } => {
95 features.contains(AvrcpTargetFeatures::SUPPORTSBROWSING)
96 }
97 Self::Controller { features, .. } => {
98 features.contains(AvrcpControllerFeatures::SUPPORTSBROWSING)
99 }
100 }
101 }
102
103 pub fn supports_absolute_volume(&self) -> bool {
108 match &self {
109 Self::Target { features, .. } => features.contains(AvrcpTargetFeatures::CATEGORY2),
110 Self::Controller { features, .. } => {
111 features.contains(AvrcpControllerFeatures::CATEGORY2)
112 }
113 }
114 }
115
116 pub fn from_search_result(
121 protocol: Vec<ProtocolDescriptor>,
122 attributes: Vec<Attribute>,
123 ) -> Result<AvrcpService, Error> {
124 let mut features: Option<u16> = None;
125 let mut service_uuids: Option<Vec<Uuid>> = None;
126 let mut profile: Option<ProfileDescriptor> = None;
127
128 let protocol = protocol
131 .iter()
132 .map(|proto| crate::profile::ProtocolDescriptor::try_from(proto))
133 .collect::<Result<Vec<_>, _>>()?;
134 let psm = psm_from_protocol(&protocol)
135 .ok_or_else(|| format_err!("AVRCP Service with no L2CAP PSM"))?;
136
137 for attr in attributes {
138 match attr.id {
139 Some(ATTR_SERVICE_CLASS_ID_LIST) => {
140 if let Some(DataElement::Sequence(seq)) = attr.element {
141 let uuids: Vec<Uuid> = seq
142 .into_iter()
143 .flatten()
144 .filter_map(|item| match *item {
145 DataElement::Uuid(uuid) => Some(uuid.into()),
146 _ => None,
147 })
148 .collect();
149 if !uuids.is_empty() {
150 service_uuids = Some(uuids);
151 }
152 }
153 }
154 Some(ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST) => {
155 if let Some(DataElement::Sequence(profiles)) = attr.element {
156 for elem in profiles {
157 let elem = elem.expect("DataElement sequence elements should exist");
158 profile = elem_to_profile_descriptor(&*elem);
159 }
160 }
161 }
162 Some(SDP_SUPPORTED_FEATURES) => {
163 if let Some(DataElement::Uint16(value)) = attr.element {
164 features = Some(value);
165 }
166 }
167 _ => {}
168 }
169 }
170
171 let (service_uuids, features, profile) = match (service_uuids, features, profile) {
172 (Some(s), Some(f), Some(p)) => (s, f, p),
173 (s, f, p) => {
174 let err = format_err!(
175 "{}{}{}missing in service attrs",
176 if s.is_some() { "" } else { "Class UUIDs " },
177 if f.is_some() { "" } else { "Features " },
178 if p.is_some() { "" } else { "Profile " }
179 );
180 return Err(err);
181 }
182 };
183
184 if psm != Psm::AVCTP {
187 info!("Found AVRCP Service with non standard PSM: {:?}", psm);
188 }
189
190 let (Some(major_version), Some(minor_version)) =
191 (profile.major_version, profile.minor_version)
192 else {
193 return Err(format_err!("ProfileDescriptor missing minor/major version"));
194 };
195 let protocol_version = AvrcpProtocolVersion(major_version, minor_version);
196
197 if service_uuids.contains(&Uuid::new16(AV_REMOTE_TARGET_CLASS)) {
198 let features = AvrcpTargetFeatures::from_bits_truncate(features);
199 return Ok(AvrcpService::Target { features, psm, protocol_version });
200 } else if service_uuids.contains(&Uuid::new16(AV_REMOTE_CLASS))
201 || service_uuids.contains(&Uuid::new16(AV_REMOTE_CONTROLLER_CLASS))
202 {
203 let features = AvrcpControllerFeatures::from_bits_truncate(features);
204 return Ok(AvrcpService::Controller { features, psm, protocol_version });
205 }
206 Err(format_err!("Failed to find any applicable services for AVRCP"))
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use assert_matches::assert_matches;
214
215 fn build_attributes(
216 service_class: bool,
217 profile_descriptor: bool,
218 sdp_features: bool,
219 ) -> Vec<Attribute> {
220 let mut attrs = Vec::new();
221 if service_class {
222 attrs.push(Attribute {
223 id: Some(ATTR_SERVICE_CLASS_ID_LIST),
224 element: Some(DataElement::Sequence(vec![Some(Box::new(DataElement::Uuid(
225 Uuid::new16(AV_REMOTE_TARGET_CLASS).into(),
226 )))])),
227 ..Default::default()
228 });
229 }
230 if profile_descriptor {
231 attrs.push(Attribute {
232 id: Some(ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST),
233 element: Some(DataElement::Sequence(vec![Some(Box::new(DataElement::Sequence(
234 vec![
235 Some(Box::new(DataElement::Uuid(Uuid::new16(4366).into()))),
236 Some(Box::new(DataElement::Uint16(0xffff))),
237 ],
238 )))])),
239 ..Default::default()
240 });
241 }
242
243 if sdp_features {
244 attrs.push(Attribute {
245 id: Some(SDP_SUPPORTED_FEATURES), element: Some(DataElement::Uint16(0xffff)),
247 ..Default::default()
248 });
249 }
250 attrs
251 }
252
253 #[fuchsia::test]
254 fn service_from_search_result() {
255 let attributes = build_attributes(true, true, true);
256 let protocol = vec![ProtocolDescriptor {
257 protocol: Some(ProtocolIdentifier::L2Cap),
258 params: Some(vec![DataElement::Uint16(20)]), ..Default::default()
260 }];
261 let service = AvrcpService::from_search_result(protocol, attributes);
262 assert_matches!(service, Ok(_));
263 }
264
265 #[fuchsia::test]
266 fn service_with_missing_features_returns_none() {
267 let no_service_class = build_attributes(false, true, true);
268 let protocol = vec![ProtocolDescriptor {
269 protocol: Some(ProtocolIdentifier::L2Cap),
270 params: Some(vec![DataElement::Uint16(20)]), ..Default::default()
272 }];
273 let service = AvrcpService::from_search_result(protocol, no_service_class);
274 assert_matches!(service, Err(_));
275
276 let no_profile_descriptor = build_attributes(true, false, true);
277 let protocol = vec![ProtocolDescriptor {
278 protocol: Some(ProtocolIdentifier::L2Cap),
279 params: Some(vec![DataElement::Uint16(20)]), ..Default::default()
281 }];
282 let service = AvrcpService::from_search_result(protocol, no_profile_descriptor);
283 assert_matches!(service, Err(_));
284
285 let no_sdp_features = build_attributes(true, true, false);
286 let protocol = vec![ProtocolDescriptor {
287 protocol: Some(ProtocolIdentifier::L2Cap),
288 params: Some(vec![DataElement::Uint16(20)]), ..Default::default()
290 }];
291 let service = AvrcpService::from_search_result(protocol, no_sdp_features);
292 assert_matches!(service, Err(_));
293 }
294
295 #[test]
296 fn service_with_missing_protocol_returns_none() {
297 let attributes = build_attributes(true, true, true);
298 let protocol = vec![];
299 let service = AvrcpService::from_search_result(protocol, attributes);
300 assert_matches!(service, Err(_));
301 }
302}