| 1 | //! Find a service's RFCOMM channel by asking the device's SDP server. |
| 2 | //! Protocol: docs/protocol/transport.md#sdp. |
| 3 | |
| 4 | use std::time::{Duration, Instant}; |
| 5 | |
| 6 | use super::{classify, socket}; |
| 7 | use crate::bt::{BdAddr, Uuid}; |
| 8 | use crate::error::{Error, Result}; |
| 9 | use crate::reader::Reader; |
| 10 | |
| 11 | /// Spec: transport.md#sockets |
| 12 | const SDP_PSM: u16 = 1; |
| 13 | /// Spec: transport.md#sdp-pdu |
| 14 | const ERROR_RESPONSE: u8 = 0x01; |
| 15 | /// Spec: transport.md#sdp-pdu |
| 16 | const SEARCH_ATTRIBUTE_REQUEST: u8 = 0x06; |
| 17 | /// Spec: transport.md#sdp-pdu |
| 18 | const SEARCH_ATTRIBUTE_RESPONSE: u8 = 0x07; |
| 19 | /// ProtocolDescriptorList. Spec: transport.md#sdp-request |
| 20 | const PROTOCOL_DESCRIPTOR_LIST: u16 = 0x0004; |
| 21 | /// Spec: transport.md#sdp-response |
| 22 | const MAX_ROUNDS: u16 = 16; |
| 23 | /// Continuation state is at most 16 bytes. Spec: transport.md#sdp-request |
| 24 | const MAX_CONTINUATION: usize = 16; |
| 25 | /// Nesting deeper than this is not a real service record. |
| 26 | const MAX_DEPTH: usize = 8; |
| 27 | |
| 28 | const TIMEOUT: Duration = Duration::from_secs(5); |
| 29 | |
| 30 | /// The RFCOMM channel of `service` on the device, or `None` if it has no such service. |
| 31 | pub fn rfcomm_channel(address: BdAddr, service: Uuid) -> Result<Option<u8>> { |
| 32 | let fd = socket::connect(address, socket::Target::L2cap { psm: SDP_PSM }, TIMEOUT) |
| 33 | .map_err(|e| classify(e, "connecting to the SDP server", false))?; |
| 34 | |
| 35 | let mut attribute_lists = Vec::new(); |
| 36 | let mut continuation = Vec::new(); |
| 37 | for transaction in 1..=MAX_ROUNDS { |
| 38 | let request = search_attribute_request(transaction, service, &continuation)?; |
| 39 | socket::write_all(&fd, &request).map_err(|e| classify(e, "sending an SDP request", false))?; |
| 40 | |
| 41 | let readable = |
| 42 | socket::wait_readable(&fd, Instant::now() + TIMEOUT).map_err(|e| classify(e, "waiting for SDP", false))?; |
| 43 | if !readable { |
| 44 | return Err(Error::Timeout("SDP service search".to_owned())); |
| 45 | } |
| 46 | let mut pdu = vec![0u8; 4096]; |
| 47 | let n = socket::read(&fd, &mut pdu).map_err(|e| classify(e, "reading SDP", false))?; |
| 48 | pdu.truncate(n); |
| 49 | |
| 50 | let response = parse_response(&pdu, transaction)?; |
| 51 | attribute_lists.extend_from_slice(response.attributes); |
| 52 | if response.continuation.is_empty() { |
| 53 | return channel_from_attribute_lists(&attribute_lists); |
| 54 | } |
| 55 | continuation = response.continuation.to_vec(); |
| 56 | } |
| 57 | Err(Error::Protocol("SDP answer did not end after 16 rounds".to_owned())) |
| 58 | } |
| 59 | |
| 60 | /// A ServiceSearchAttributeRequest for one 128-bit UUID, asking only for the |
| 61 | /// ProtocolDescriptorList. Spec: transport.md#sdp-request |
| 62 | fn search_attribute_request(transaction: u16, service: Uuid, continuation: &[u8]) -> Result<Vec<u8>> { |
| 63 | let continuation_len = u8::try_from(continuation.len()) |
| 64 | .ok() |
| 65 | .filter(|&len| usize::from(len) <= MAX_CONTINUATION) |
| 66 | .ok_or_else(|| Error::Protocol("SDP continuation state too long".to_owned()))?; |
| 67 | |
| 68 | let mut parameters = vec![0x35, 0x11, 0x1C]; // sequence of 17 bytes: one 128-bit UUID |
| 69 | parameters.extend(service.to_be_bytes()); |
| 70 | parameters.extend([0xFF, 0xFF]); // maximum attribute byte count |
| 71 | parameters.extend([0x35, 0x03, 0x09]); // sequence of 3 bytes: one 16-bit attribute id |
| 72 | parameters.extend(PROTOCOL_DESCRIPTOR_LIST.to_be_bytes()); |
| 73 | parameters.push(continuation_len); |
| 74 | parameters.extend(continuation); |
| 75 | |
| 76 | let parameter_len = |
| 77 | u16::try_from(parameters.len()).map_err(|_| Error::Protocol("SDP request too long".to_owned()))?; |
| 78 | let mut pdu = vec![SEARCH_ATTRIBUTE_REQUEST]; |
| 79 | pdu.extend(transaction.to_be_bytes()); |
| 80 | pdu.extend(parameter_len.to_be_bytes()); |
| 81 | pdu.extend(parameters); |
| 82 | Ok(pdu) |
| 83 | } |
| 84 | |
| 85 | struct Response<'a> { |
| 86 | attributes: &'a [u8], |
| 87 | continuation: &'a [u8], |
| 88 | } |
| 89 | |
| 90 | /// Spec: transport.md#sdp-response |
| 91 | fn parse_response(pdu: &[u8], transaction: u16) -> Result<Response<'_>> { |
| 92 | let mut header = Reader::new(pdu, "SDP response"); |
| 93 | let id = header.u8()?; |
| 94 | let answered = header.u16_be()?; |
| 95 | let len = header.u16_be()?; |
| 96 | let mut parameters = Reader::new(header.take(usize::from(len))?, "SDP response"); |
| 97 | match id { |
| 98 | SEARCH_ATTRIBUTE_RESPONSE if answered == transaction => { |
| 99 | let count = parameters.u16_be()?; |
| 100 | let attributes = parameters.take(usize::from(count))?; |
| 101 | let continuation_len = parameters.u8()?; |
| 102 | if usize::from(continuation_len) > MAX_CONTINUATION { |
| 103 | return Err(Error::Protocol("SDP continuation state too long".to_owned())); |
| 104 | } |
| 105 | let continuation = parameters.take(usize::from(continuation_len))?; |
| 106 | Ok(Response { |
| 107 | attributes, |
| 108 | continuation, |
| 109 | }) |
| 110 | } |
| 111 | SEARCH_ATTRIBUTE_RESPONSE => Err(Error::Protocol(format!( |
| 112 | "SDP answered transaction {answered}, expected {transaction}" |
| 113 | ))), |
| 114 | ERROR_RESPONSE => Err(Error::Protocol(format!( |
| 115 | "SDP error response 0x{:04X}", |
| 116 | parameters.u16_be()? |
| 117 | ))), |
| 118 | other => Err(Error::Protocol(format!("unexpected SDP PDU 0x{other:02X}"))), |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /// The parts of an SDP data element budctl looks at. Spec: transport.md#sdp-data-elements |
| 123 | #[derive(Debug, PartialEq)] |
| 124 | enum Element { |
| 125 | Uint(u64), |
| 126 | Uuid(Uuid), |
| 127 | /// A sequence or an alternative. |
| 128 | List(Vec<Element>), |
| 129 | Other, |
| 130 | } |
| 131 | |
| 132 | fn element(r: &mut Reader, depth: usize) -> Result<Element> { |
| 133 | if depth > MAX_DEPTH { |
| 134 | return Err(Error::Protocol("SDP data nested too deeply".to_owned())); |
| 135 | } |
| 136 | let header = r.u8()?; |
| 137 | let (kind, size) = (header >> 3, header & 0x07); |
| 138 | let len = match size { |
| 139 | 0 if kind == 0 => 0, // nil |
| 140 | 0 => 1, |
| 141 | 1 => 2, |
| 142 | 2 => 4, |
| 143 | 3 => 8, |
| 144 | 4 => 16, |
| 145 | 5 => usize::from(r.u8()?), |
| 146 | 6 => usize::from(r.u16_be()?), |
| 147 | _ => usize::try_from(r.u32_be()?).map_err(|_| Error::Protocol("SDP element too long".to_owned()))?, |
| 148 | }; |
| 149 | let data = r.take(len)?; |
| 150 | Ok(match kind { |
| 151 | 1 => match data.len() { |
| 152 | 1 | 2 | 4 | 8 => Element::Uint(data.iter().fold(0, |n, &b| (n << 8) | u64::from(b))), |
| 153 | _ => Element::Other, |
| 154 | }, |
| 155 | 3 => Element::Uuid(Uuid::from_sdp(data)?), |
| 156 | 6 | 7 => { |
| 157 | let mut inner = Reader::new(data, "SDP sequence"); |
| 158 | let mut items = Vec::new(); |
| 159 | while !inner.is_empty() { |
| 160 | items.push(element(&mut inner, depth + 1)?); |
| 161 | } |
| 162 | Element::List(items) |
| 163 | } |
| 164 | _ => Element::Other, |
| 165 | }) |
| 166 | } |
| 167 | |
| 168 | /// The RFCOMM channel in the (joined) AttributeLists of a ServiceSearchAttributeResponse. |
| 169 | /// Spec: transport.md#sdp-rfcomm-channel |
| 170 | fn channel_from_attribute_lists(bytes: &[u8]) -> Result<Option<u8>> { |
| 171 | let mut r = Reader::new(bytes, "SDP attribute lists"); |
| 172 | let Element::List(records) = element(&mut r, 0)? else { |
| 173 | return Err(Error::Protocol("SDP attribute lists are not a sequence".to_owned())); |
| 174 | }; |
| 175 | for record in &records { |
| 176 | let Element::List(attributes) = record else { continue }; |
| 177 | for pair in attributes.chunks(2) { |
| 178 | if let [Element::Uint(id), value] = pair |
| 179 | && *id == u64::from(PROTOCOL_DESCRIPTOR_LIST) |
| 180 | && let Some(channel) = find_rfcomm(value) |
| 181 | { |
| 182 | return u8::try_from(channel) |
| 183 | .map(Some) |
| 184 | .map_err(|_| Error::Protocol(format!("RFCOMM channel {channel} out of range"))); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | Ok(None) |
| 189 | } |
| 190 | |
| 191 | /// A protocol descriptor is a sequence that starts with the protocol's UUID; for RFCOMM the |
| 192 | /// channel number comes next. Searching nested lists also covers records with alternatives. |
| 193 | fn find_rfcomm(element: &Element) -> Option<u64> { |
| 194 | let Element::List(items) = element else { return None }; |
| 195 | if let [Element::Uuid(uuid), Element::Uint(channel), ..] = items.as_slice() |
| 196 | && *uuid == Uuid::RFCOMM |
| 197 | { |
| 198 | return Some(*channel); |
| 199 | } |
| 200 | items.iter().find_map(find_rfcomm) |
| 201 | } |
| 202 | |
| 203 | #[cfg(test)] |
| 204 | mod tests { |
| 205 | use super::*; |
| 206 | use crate::transport::mock::hex; |
| 207 | |
| 208 | const SERVICE: Uuid = Uuid::from_u128(0x0000_079A_D102_11E1_9B23_0002_5B00_A5A5); |
| 209 | |
| 210 | /// One record: ProtocolDescriptorList = (L2CAP), (RFCOMM, channel 15). |
| 211 | /// Spec: transport.md#sdp-rfcomm-channel |
| 212 | const ATTRIBUTE_LISTS: &str = "35 13 35 11 09 00 04 35 0C 35 03 19 01 00 35 05 19 00 03 08 0F"; |
| 213 | |
| 214 | #[test] |
| 215 | fn request_layout() { |
| 216 | let pdu = search_attribute_request(1, SERVICE, &[]).unwrap(); |
| 217 | let mut expected = hex("06 00 01 00 1B 35 11 1C"); |
| 218 | expected.extend(SERVICE.to_be_bytes()); |
| 219 | expected.extend(hex("FF FF 35 03 09 00 04 00")); |
| 220 | assert_eq!(pdu, expected); |
| 221 | |
| 222 | let next = search_attribute_request(2, SERVICE, &[0xAB, 0xCD]).unwrap(); |
| 223 | assert_eq!(next.get(1..5), Some(&hex("00 02 00 1D")[..])); |
| 224 | assert!(next.ends_with(&hex("00 04 02 AB CD"))); |
| 225 | } |
| 226 | |
| 227 | #[test] |
| 228 | fn finds_the_channel_in_one_response() { |
| 229 | let mut pdu = hex("07 00 01 00 18 00 15"); |
| 230 | pdu.extend(hex(ATTRIBUTE_LISTS)); |
| 231 | pdu.push(0x00); |
| 232 | let response = parse_response(&pdu, 1).unwrap(); |
| 233 | assert!(response.continuation.is_empty()); |
| 234 | assert_eq!(channel_from_attribute_lists(response.attributes).unwrap(), Some(15)); |
| 235 | } |
| 236 | |
| 237 | #[test] |
| 238 | fn joins_continued_responses() { |
| 239 | let lists = hex(ATTRIBUTE_LISTS); |
| 240 | let (first, second) = lists.split_at(9); |
| 241 | let mut pdu1 = vec![0x07, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x09]; |
| 242 | pdu1.extend(first); |
| 243 | pdu1.extend([0x02, 0xAB, 0xCD]); |
| 244 | let r1 = parse_response(&pdu1, 1).unwrap(); |
| 245 | assert_eq!(r1.continuation, &[0xAB, 0xCD]); |
| 246 | |
| 247 | let mut pdu2 = vec![0x07, 0x00, 0x02, 0x00, 0x0F, 0x00, 0x0C]; |
| 248 | pdu2.extend(second); |
| 249 | pdu2.push(0x00); |
| 250 | let r2 = parse_response(&pdu2, 2).unwrap(); |
| 251 | |
| 252 | let joined = [r1.attributes, r2.attributes].concat(); |
| 253 | assert_eq!(channel_from_attribute_lists(&joined).unwrap(), Some(15)); |
| 254 | } |
| 255 | |
| 256 | #[test] |
| 257 | fn no_rfcomm_descriptor_means_no_channel() { |
| 258 | // ProtocolDescriptorList with L2CAP only. |
| 259 | let lists = hex("35 0C 35 0A 09 00 04 35 05 35 03 19 01 00"); |
| 260 | assert_eq!(channel_from_attribute_lists(&lists).unwrap(), None); |
| 261 | // No matching record at all. |
| 262 | assert_eq!(channel_from_attribute_lists(&hex("35 00")).unwrap(), None); |
| 263 | } |
| 264 | |
| 265 | #[test] |
| 266 | fn errors_and_garbage_are_protocol_errors() { |
| 267 | assert!(matches!( |
| 268 | parse_response(&hex("01 00 01 00 02 00 03"), 1), |
| 269 | Err(Error::Protocol(_)) |
| 270 | )); |
| 271 | assert!(matches!( |
| 272 | parse_response(&hex("07 00 09 00 03 00 00 00"), 1), |
| 273 | Err(Error::Protocol(_)) |
| 274 | )); |
| 275 | assert!(parse_response(&hex("07 00 01 00 05 00 09"), 1).is_err()); |
| 276 | assert!(channel_from_attribute_lists(&hex("35 05 35")).is_err()); |
| 277 | assert!(channel_from_attribute_lists(&hex("09 00 04")).is_err()); |
| 278 | // Deep nesting is refused instead of recursing without bound. |
| 279 | let mut nested = Vec::new(); |
| 280 | for _ in 0..20 { |
| 281 | let mut outer = vec![0x35, u8::try_from(nested.len()).unwrap()]; |
| 282 | outer.extend(nested); |
| 283 | nested = outer; |
| 284 | } |
| 285 | assert!(channel_from_attribute_lists(&nested).is_err()); |
| 286 | } |
| 287 | } |