Krolyxon
Commit 6e8f058

Branches

Refs →

budctlcratesbudctl-coresrctransportsdp.rs

287 lines · 10.9 KB · Rust

1//! Find a service's RFCOMM channel by asking the device's SDP server.
2//! Protocol: docs/protocol/transport.md#sdp.
3
4use std::time::{Duration, Instant};
5
6use super::{classify, socket};
7use crate::bt::{BdAddr, Uuid};
8use crate::error::{Error, Result};
9use crate::reader::Reader;
10
11/// Spec: transport.md#sockets
12const SDP_PSM: u16 = 1;
13/// Spec: transport.md#sdp-pdu
14const ERROR_RESPONSE: u8 = 0x01;
15/// Spec: transport.md#sdp-pdu
16const SEARCH_ATTRIBUTE_REQUEST: u8 = 0x06;
17/// Spec: transport.md#sdp-pdu
18const SEARCH_ATTRIBUTE_RESPONSE: u8 = 0x07;
19/// ProtocolDescriptorList. Spec: transport.md#sdp-request
20const PROTOCOL_DESCRIPTOR_LIST: u16 = 0x0004;
21/// Spec: transport.md#sdp-response
22const MAX_ROUNDS: u16 = 16;
23/// Continuation state is at most 16 bytes. Spec: transport.md#sdp-request
24const MAX_CONTINUATION: usize = 16;
25/// Nesting deeper than this is not a real service record.
26const MAX_DEPTH: usize = 8;
27
28const TIMEOUT: Duration = Duration::from_secs(5);
29
30/// The RFCOMM channel of `service` on the device, or `None` if it has no such service.
31pub 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
62fn 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
85struct Response<'a> {
86 attributes: &'a [u8],
87 continuation: &'a [u8],
88}
89
90/// Spec: transport.md#sdp-response
91fn 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)]
124enum Element {
125 Uint(u64),
126 Uuid(Uuid),
127 /// A sequence or an alternative.
128 List(Vec<Element>),
129 Other,
130}
131
132fn 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
170fn 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.
193fn 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)]
204mod 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}