1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/QByteArray>
5#include <QtCore/QDebug>
6#include <QtCore/QUrl>
7#include <stdio.h>
8#include <string>
9#include <bluetooth/bluetooth.h>
10#include <bluetooth/sdp.h>
11#include <bluetooth/sdp_lib.h>
12
13#define RETURN_SUCCESS 0
14#define RETURN_USAGE 1
15#define RETURN_INVALPARAM 2
16#define RETURN_SDP_ERROR 3
17
18void usage()
19{
20 fprintf(stderr, format: "Usage:\n");
21 fprintf(stderr, format: "\tsdpscanner <remote bdaddr> <local bdaddr> [Options] ({uuids})\n\n");
22 fprintf(stderr, format: "Performs an SDP scan on remote device, using the SDP server\n"
23 "represented by the local Bluetooth device.\n\n"
24 "Options:\n"
25 " -p Show scan results in human-readable form\n"
26 " -u [list of uuids] List of uuids which should be scanned for.\n"
27 " Each uuid must be enclosed in {}.\n"
28 " If the list is empty PUBLIC_BROWSE_GROUP scan is used.\n");
29}
30
31#define BUFFER_SIZE 1024
32
33static void parseAttributeValues(sdp_data_t *data, int indentation, QByteArray &xmlOutput)
34{
35 if (!data)
36 return;
37
38 const int length = indentation*2 + 1;
39 QByteArray indentString(length, ' ');
40
41 char snBuffer[BUFFER_SIZE];
42
43 xmlOutput.append(a: indentString);
44
45 // deal with every dtd type
46 switch (data->dtd) {
47 case SDP_DATA_NIL:
48 xmlOutput.append(s: "<nil/>\n");
49 break;
50 case SDP_UINT8:
51 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<uint8 value=\"0x%02x\"/>\n", data->val.uint8);
52 xmlOutput.append(s: snBuffer);
53 break;
54 case SDP_UINT16:
55 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<uint16 value=\"0x%04x\"/>\n", data->val.uint16);
56 xmlOutput.append(s: snBuffer);
57 break;
58 case SDP_UINT32:
59 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<uint32 value=\"0x%08x\"/>\n", data->val.uint32);
60 xmlOutput.append(s: snBuffer);
61 break;
62 case SDP_UINT64:
63 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<uint64 value=\"0x%016x\"/>\n", data->val.uint64);
64 xmlOutput.append(s: snBuffer);
65 break;
66 case SDP_UINT128:
67 xmlOutput.append(s: "<uint128 value=\"0x");
68 for (int i = 0; i < 16; i++)
69 ::sprintf(s: &snBuffer[i * 2], format: "%02x", data->val.uint128.data[i]);
70 xmlOutput.append(s: snBuffer);
71 xmlOutput.append(s: "\"/>\n");
72 break;
73 case SDP_INT8:
74 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<int8 value=\"%d\"/>/n", data->val.int8);
75 xmlOutput.append(s: snBuffer);
76 break;
77 case SDP_INT16:
78 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<int16 value=\"%d\"/>/n", data->val.int16);
79 xmlOutput.append(s: snBuffer);
80 break;
81 case SDP_INT32:
82 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<int32 value=\"%d\"/>/n", data->val.int32);
83 xmlOutput.append(s: snBuffer);
84 break;
85 case SDP_INT64:
86 qsnprintf(str: snBuffer, BUFFER_SIZE, fmt: "<int64 value=\"%d\"/>/n", data->val.int64);
87 xmlOutput.append(s: snBuffer);
88 break;
89 case SDP_INT128:
90 xmlOutput.append(s: "<int128 value=\"0x");
91 for (int i = 0; i < 16; i++)
92 ::sprintf(s: &snBuffer[i * 2], format: "%02x", data->val.int128.data[i]);
93 xmlOutput.append(s: snBuffer);
94 xmlOutput.append(s: "\"/>\n");
95 break;
96 case SDP_UUID_UNSPEC:
97 break;
98 case SDP_UUID16:
99 case SDP_UUID32:
100 xmlOutput.append(s: "<uuid value=\"0x");
101 sdp_uuid2strn(uuid: &(data->val.uuid), str: snBuffer, BUFFER_SIZE);
102 xmlOutput.append(s: snBuffer);
103 xmlOutput.append(s: "\"/>\n");
104 break;
105 case SDP_UUID128:
106 xmlOutput.append(s: "<uuid value=\"");
107 sdp_uuid2strn(uuid: &(data->val.uuid), str: snBuffer, BUFFER_SIZE);
108 xmlOutput.append(s: snBuffer);
109 xmlOutput.append(s: "\"/>\n");
110 break;
111 case SDP_TEXT_STR_UNSPEC:
112 break;
113 case SDP_TEXT_STR8:
114 case SDP_TEXT_STR16:
115 case SDP_TEXT_STR32:
116 {
117 xmlOutput.append(s: "<text ");
118 QByteArray text = QByteArray::fromRawData(data: data->val.str, size: data->unitSize);
119
120 bool hasNonPrintableChar = false;
121 for (qsizetype i = 0; i < text.size(); ++i) {
122 if (text[i] == '\0') {
123 text.resize(size: i); // cut trailing content
124 break;
125 } else if (!isprint(text[i])) {
126 hasNonPrintableChar = true;
127 const auto firstNullIdx = text.indexOf(c: '\0');
128 if (firstNullIdx > 0)
129 text.resize(size: firstNullIdx); // cut trailing content
130 break;
131 }
132 }
133
134 if (hasNonPrintableChar) {
135 xmlOutput.append(s: "encoding=\"hex\" value=\"");
136 xmlOutput.append(a: text.toHex());
137 } else {
138 text.replace(before: '&', after: "&amp;");
139 text.replace(before: '<', after: "&lt;");
140 text.replace(before: '>', after: "&gt;");
141 text.replace(before: '"', after: "&quot;");
142
143 xmlOutput.append(s: "value=\"");
144 xmlOutput.append(a: text);
145 }
146
147 xmlOutput.append(s: "\"/>\n");
148 break;
149 }
150 case SDP_BOOL:
151 if (data->val.uint8)
152 xmlOutput.append(s: "<boolean value=\"true\"/>\n");
153 else
154 xmlOutput.append(s: "<boolean value=\"false\"/>\n");
155 break;
156 case SDP_SEQ_UNSPEC:
157 break;
158 case SDP_SEQ8:
159 case SDP_SEQ16:
160 case SDP_SEQ32:
161 xmlOutput.append(s: "<sequence>\n");
162 parseAttributeValues(data: data->val.dataseq, indentation: indentation + 1, xmlOutput);
163 xmlOutput.append(a: indentString);
164 xmlOutput.append(s: "</sequence>\n");
165 break;
166 case SDP_ALT_UNSPEC:
167 break;
168 case SDP_ALT8:
169 case SDP_ALT16:
170 case SDP_ALT32:
171 xmlOutput.append(s: "<alternate>\n");
172 parseAttributeValues(data: data->val.dataseq, indentation: indentation + 1, xmlOutput);
173 xmlOutput.append(a: indentString);
174 xmlOutput.append(s: "</alternate>\n");
175 break;
176 case SDP_URL_STR_UNSPEC:
177 break;
178 case SDP_URL_STR8:
179 case SDP_URL_STR16:
180 case SDP_URL_STR32:
181 {
182 xmlOutput.append(s: "<url value=\"");
183 const QByteArray urlData =
184 QByteArray::fromRawData(data: data->val.str, size: qstrnlen(str: data->val.str, maxlen: data->unitSize));
185 const QUrl url = QUrl::fromEncoded(url: urlData);
186 // Encoded url %-encodes all of the XML special characters except '&',
187 // so we need to do that manually
188 xmlOutput.append(a: url.toEncoded().replace(before: '&', after: "&amp;"));
189 xmlOutput.append(s: "\"/>\n");
190 break;
191 }
192 default:
193 fprintf(stderr, format: "Unknown dtd type\n");
194 }
195
196 parseAttributeValues(data: data->next, indentation, xmlOutput);
197}
198
199static void parseAttribute(void *value, void *extraData)
200{
201 sdp_data_t *data = (sdp_data_t *) value;
202 QByteArray *xmlOutput = static_cast<QByteArray *>(extraData);
203
204 char buffer[BUFFER_SIZE];
205
206 ::qsnprintf(str: buffer, BUFFER_SIZE, fmt: " <attribute id=\"0x%04x\">\n", data->attrId);
207 xmlOutput->append(s: buffer);
208
209 parseAttributeValues(data, indentation: 2, xmlOutput&: *xmlOutput);
210
211 xmlOutput->append(s: " </attribute>\n");
212}
213
214// the resulting xml output is based on the already used xml parser
215QByteArray parseSdpRecord(sdp_record_t *record)
216{
217 if (!record || !record->attrlist)
218 return QByteArray();
219
220 QByteArray xmlOutput;
221
222 xmlOutput.append(s: "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<record>\n");
223
224 sdp_list_foreach(list: record->attrlist, f: parseAttribute, u: &xmlOutput);
225 xmlOutput.append(s: "</record>");
226
227 return xmlOutput;
228}
229
230
231int main(int argc, char **argv)
232{
233 if (argc < 3) {
234 usage();
235 return RETURN_USAGE;
236 }
237
238 fprintf(stderr, format: "SDP for %s %s\n", argv[1], argv[2]);
239
240 bdaddr_t remote;
241 bdaddr_t local;
242 int result = str2ba(str: argv[1], ba: &remote);
243 if (result < 0) {
244 fprintf(stderr, format: "Invalid remote address: %s\n", argv[1]);
245 return RETURN_INVALPARAM;
246 }
247
248 result = str2ba(str: argv[2], ba: &local);
249 if (result < 0) {
250 fprintf(stderr, format: "Invalid local address: %s\n", argv[2]);
251 return RETURN_INVALPARAM;
252 }
253
254 bool showHumanReadable = false;
255 std::vector<std::string> targetServices;
256
257 for (int i = 3; i < argc; i++) {
258 if (argv[i][0] != '-') {
259 usage();
260 return RETURN_USAGE;
261 }
262
263 switch (argv[i][1])
264 {
265 case 'p':
266 showHumanReadable = true;
267 break;
268 case 'u':
269 i++;
270
271 for ( ; i < argc && argv[i][0] == '{'; i++)
272 targetServices.push_back(x: argv[i]);
273
274 i--; // outer loop increments again
275 break;
276 default:
277 fprintf(stderr, format: "Wrong argument: %s\n", argv[i]);
278 usage();
279 return RETURN_USAGE;
280 }
281 }
282
283 std::vector<uuid_t> uuids;
284 for (std::vector<std::string>::const_iterator iter = targetServices.cbegin();
285 iter != targetServices.cend(); ++iter) {
286
287 uint128_t temp128;
288 uint16_t field1, field2, field3, field5;
289 uint32_t field0, field4;
290
291 fprintf(stderr, format: "Target scan for %s\n", (*iter).c_str());
292 if (sscanf(s: (*iter).c_str(), format: "{%08x-%04hx-%04hx-%04hx-%08x%04hx}", &field0,
293 &field1, &field2, &field3, &field4, &field5) != 6) {
294 fprintf(stderr, format: "Skipping invalid uuid: %s\n", ((*iter).c_str()));
295 continue;
296 }
297
298 // we need uuid_t conversion based on
299 // http://www.spinics.net/lists/linux-bluetooth/msg20356.html
300 field0 = htonl(hostlong: field0);
301 field4 = htonl(hostlong: field4);
302 field1 = htons(hostshort: field1);
303 field2 = htons(hostshort: field2);
304 field3 = htons(hostshort: field3);
305 field5 = htons(hostshort: field5);
306
307 uint8_t* temp = (uint8_t*) &temp128;
308 memcpy(dest: &temp[0], src: &field0, n: 4);
309 memcpy(dest: &temp[4], src: &field1, n: 2);
310 memcpy(dest: &temp[6], src: &field2, n: 2);
311 memcpy(dest: &temp[8], src: &field3, n: 2);
312 memcpy(dest: &temp[10], src: &field4, n: 4);
313 memcpy(dest: &temp[14], src: &field5, n: 2);
314
315 uuid_t sdpUuid;
316 sdp_uuid128_create(uuid: &sdpUuid, data: &temp128);
317 uuids.push_back(x: sdpUuid);
318 }
319
320 sdp_session_t *session = sdp_connect( src: &local, dst: &remote, SDP_RETRY_IF_BUSY);
321 if (!session) {
322 //try one more time if first time failed
323 session = sdp_connect( src: &local, dst: &remote, SDP_RETRY_IF_BUSY);
324 }
325
326 if (!session) {
327 fprintf(stderr, format: "Cannot establish sdp session\n");
328 return RETURN_SDP_ERROR;
329 }
330
331 // set the filter for service matches
332 if (uuids.empty()) {
333 fprintf(stderr, format: "Using PUBLIC_BROWSE_GROUP for SDP search\n");
334 uuid_t publicBrowseGroupUuid;
335 sdp_uuid16_create(uuid: &publicBrowseGroupUuid, PUBLIC_BROWSE_GROUP);
336 uuids.push_back(x: publicBrowseGroupUuid);
337 }
338
339 uint32_t attributeRange = 0x0000ffff; //all attributes
340 sdp_list_t *attributes;
341 attributes = sdp_list_append(list: nullptr, d: &attributeRange);
342
343 sdp_list_t *sdpResults, *sdpIter;
344 sdp_list_t *totalResults = nullptr;
345 sdp_list_t* serviceFilter;
346
347 for (uuid_t &uuid : uuids) { // can't be const, d/t sdp_list_append signature
348 serviceFilter = sdp_list_append(list: nullptr, d: &uuid);
349 result = sdp_service_search_attr_req(session, search: serviceFilter,
350 reqtype: SDP_ATTR_REQ_RANGE,
351 attrid_list: attributes, rsp_list: &sdpResults);
352 sdp_list_free(list: serviceFilter, f: nullptr);
353 if (result != 0) {
354 fprintf(stderr, format: "sdp_service_search_attr_req failed\n");
355 sdp_list_free(list: attributes, f: nullptr);
356 sdp_close(session);
357 return RETURN_SDP_ERROR;
358 }
359
360 if (!sdpResults)
361 continue;
362
363 if (!totalResults) {
364 totalResults = sdpResults;
365 sdpIter = totalResults;
366 } else {
367 // attach each new result list to the end of totalResults
368 sdpIter->next = sdpResults;
369 }
370
371 while (sdpIter->next) // skip to end of list
372 sdpIter = sdpIter->next;
373 }
374 sdp_list_free(list: attributes, f: nullptr);
375
376 // start XML generation from the front
377 sdpResults = totalResults;
378
379 QByteArray total;
380 while (sdpResults) {
381 sdp_record_t *record = (sdp_record_t *) sdpResults->data;
382
383 const QByteArray xml = parseSdpRecord(record);
384 total += xml;
385
386 sdpIter = sdpResults;
387 sdpResults = sdpResults->next;
388 free(ptr: sdpIter);
389 sdp_record_free(rec: record);
390 }
391
392 if (!total.isEmpty()) {
393 if (showHumanReadable)
394 printf(format: "%s", total.constData());
395 else
396 printf(format: "%s", total.toBase64().constData());
397 }
398
399 sdp_close(session);
400
401 return RETURN_SUCCESS;
402}
403

source code of qtconnectivity/src/tools/sdpscanner/main.cpp