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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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