Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include <QtCore/QByteArray>
++ #include <QtCore/QDebug>
++ #include <QtCore/QUrl>
++ #include <stdio.h>
++ #include <string>
++ #include <bluetooth/bluetooth.h>
++ #include <bluetooth/sdp.h>
++ #include <bluetooth/sdp_lib.h>
++
++ #include <cstdio>
++
++ #define RETURN_SUCCESS 0
++ #define RETURN_USAGE 1
++ #define RETURN_INVALPARAM 2
++ #define RETURN_SDP_ERROR 3
++
++ void usage()
++ {
++ fprintf(stderr, "Usage:\n");
++ fprintf(stderr, "\tsdpscanner <remote bdaddr> <local bdaddr> [Options] ({uuids})\n\n");
++ fprintf(stderr, "Performs an SDP scan on remote device, using the SDP server\n"
++ "represented by the local Bluetooth device.\n\n"
++ "Options:\n"
++ " -p Show scan results in human-readable form\n"
++ " -u [list of uuids] List of uuids which should be scanned for.\n"
++ " Each uuid must be enclosed in {}.\n"
++ " If the list is empty PUBLIC_BROWSE_GROUP scan is used.\n");
++ }
++
++ #define BUFFER_SIZE 1024
++
++ static void parseAttributeValues(sdp_data_t *data, int indentation,
++ QByteArray &xmlOutput, int depth = 0)
++ {
++ // same as in src/bluetooth/qbluetoothservicediscoveryagent_p.h
++ static constexpr int kMaxSdpRecursionDepth = 64;
++
++ if (depth > kMaxSdpRecursionDepth)
++ return;
++
++ while (data) {
++ const int length = indentation*2 + 1;
++ QByteArray indentString(length, ' ');
++
++ char snBuffer[BUFFER_SIZE];
++
++ xmlOutput.append(indentString);
++
++ // deal with every dtd type
++ switch (data->dtd) {
++ case SDP_DATA_NIL:
++ xmlOutput.append("<nil/>\n");
++ break;
++ case SDP_UINT8:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<uint8 value=\"0x%02x\"/>\n", data->val.uint8);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_UINT16:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<uint16 value=\"0x%04x\"/>\n", data->val.uint16);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_UINT32:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<uint32 value=\"0x%08x\"/>\n", data->val.uint32);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_UINT64:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<uint64 value=\"0x%016llx\"/>\n",
++ qulonglong(data->val.uint64));
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_UINT128:
++ xmlOutput.append("<uint128 value=\"0x");
++ for (int i = 0; i < 16; i++)
++ std::sprintf(&snBuffer[i * 2], "%02x", data->val.uint128.data[i]);
++ xmlOutput.append(snBuffer);
++ xmlOutput.append("\"/>\n");
++ break;
++ case SDP_INT8:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<int8 value=\"%d\"/>/n", data->val.int8);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_INT16:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<int16 value=\"%d\"/>/n", data->val.int16);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_INT32:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<int32 value=\"%d\"/>/n", data->val.int32);
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_INT64:
++ std::snprintf(snBuffer, BUFFER_SIZE, "<int64 value=\"%lld\"/>/n",
++ qlonglong(data->val.int64));
++ xmlOutput.append(snBuffer);
++ break;
++ case SDP_INT128:
++ xmlOutput.append("<int128 value=\"0x");
++ for (int i = 0; i < 16; i++)
++ std::sprintf(&snBuffer[i * 2], "%02x", data->val.int128.data[i]);
++ xmlOutput.append(snBuffer);
++ xmlOutput.append("\"/>\n");
++ break;
++ case SDP_UUID_UNSPEC:
++ break;
++ case SDP_UUID16:
++ case SDP_UUID32:
++ xmlOutput.append("<uuid value=\"0x");
++ sdp_uuid2strn(&(data->val.uuid), snBuffer, BUFFER_SIZE);
++ xmlOutput.append(snBuffer);
++ xmlOutput.append("\"/>\n");
++ break;
++ case SDP_UUID128:
++ xmlOutput.append("<uuid value=\"");
++ sdp_uuid2strn(&(data->val.uuid), snBuffer, BUFFER_SIZE);
++ xmlOutput.append(snBuffer);
++ xmlOutput.append("\"/>\n");
++ break;
++ case SDP_TEXT_STR_UNSPEC:
++ break;
++ case SDP_TEXT_STR8:
++ case SDP_TEXT_STR16:
++ case SDP_TEXT_STR32:
++ {
++ xmlOutput.append("<text ");
++ QByteArray text = QByteArray::fromRawData(data->val.str, data->unitSize);
++
++ bool hasNonPrintableChar = false;
++ for (qsizetype i = 0; i < text.size(); ++i) {
++ if (text[i] == '\0') {
++ text.resize(i); // cut trailing content
++ break;
++ } else if (!isprint(text[i])) {
++ hasNonPrintableChar = true;
++ const auto firstNullIdx = text.indexOf('\0');
++ if (firstNullIdx > 0)
++ text.resize(firstNullIdx); // cut trailing content
++ break;
++ }
++ }
++
++ if (hasNonPrintableChar) {
++ xmlOutput.append("encoding=\"hex\" value=\"");
++ xmlOutput.append(text.toHex());
++ } else {
++ text.replace('&', "&amp;");
++ text.replace('<', "&lt;");
++ text.replace('>', "&gt;");
++ text.replace('"', "&quot;");
++
++ xmlOutput.append("value=\"");
++ xmlOutput.append(text);
++ }
++
++ xmlOutput.append("\"/>\n");
++ break;
++ }
++ case SDP_BOOL:
++ if (data->val.uint8)
++ xmlOutput.append("<boolean value=\"true\"/>\n");
++ else
++ xmlOutput.append("<boolean value=\"false\"/>\n");
++ break;
++ case SDP_SEQ_UNSPEC:
++ break;
++ case SDP_SEQ8:
++ case SDP_SEQ16:
++ case SDP_SEQ32:
++ xmlOutput.append("<sequence>\n");
++ parseAttributeValues(data->val.dataseq, indentation + 1, xmlOutput, depth + 1);
++ xmlOutput.append(indentString);
++ xmlOutput.append("</sequence>\n");
++ break;
++ case SDP_ALT_UNSPEC:
++ break;
++ case SDP_ALT8:
++ case SDP_ALT16:
++ case SDP_ALT32:
++ xmlOutput.append("<alternate>\n");
++ parseAttributeValues(data->val.dataseq, indentation + 1, xmlOutput, depth + 1);
++ xmlOutput.append(indentString);
++ xmlOutput.append("</alternate>\n");
++ break;
++ case SDP_URL_STR_UNSPEC:
++ break;
++ case SDP_URL_STR8:
++ case SDP_URL_STR16:
++ case SDP_URL_STR32:
++ {
++ xmlOutput.append("<url value=\"");
++ const QByteArray urlData =
++ QByteArray::fromRawData(data->val.str, qstrnlen(data->val.str, data->unitSize));
++ const QUrl url = QUrl::fromEncoded(urlData);
++ // Encoded url %-encodes all of the XML special characters except '&',
++ // so we need to do that manually
++ xmlOutput.append(url.toEncoded().replace('&', "&amp;"));
++ xmlOutput.append("\"/>\n");
++ break;
++ }
++ default:
++ fprintf(stderr, "Unknown dtd type\n");
++ }
++
++ data = data->next;
++ }
++ }
++
++ static void parseAttribute(void *value, void *extraData)
++ {
++ sdp_data_t *data = (sdp_data_t *) value;
++ QByteArray *xmlOutput = static_cast<QByteArray *>(extraData);
++
++ char buffer[BUFFER_SIZE];
++
++ std::snprintf(buffer, BUFFER_SIZE, " <attribute id=\"0x%04x\">\n", data->attrId);
++ xmlOutput->append(buffer);
++
++ parseAttributeValues(data, 2, *xmlOutput);
++
++ xmlOutput->append(" </attribute>\n");
++ }
++
++ // the resulting xml output is based on the already used xml parser
++ QByteArray parseSdpRecord(sdp_record_t *record)
++ {
++ if (!record || !record->attrlist)
++ return QByteArray();
++
++ QByteArray xmlOutput;
++
++ xmlOutput.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<record>\n");
++
++ sdp_list_foreach(record->attrlist, parseAttribute, &xmlOutput);
++ xmlOutput.append("</record>");
++
++ return xmlOutput;
++ }
++
++
++ int main(int argc, char **argv)
++ {
++ if (argc < 3) {
++ usage();
++ return RETURN_USAGE;
++ }
++
++ fprintf(stderr, "SDP for %s %s\n", argv[1], argv[2]);
++
++ bdaddr_t remote;
++ bdaddr_t local;
++ int result = str2ba(argv[1], &remote);
++ if (result < 0) {
++ fprintf(stderr, "Invalid remote address: %s\n", argv[1]);
++ return RETURN_INVALPARAM;
++ }
++
++ result = str2ba(argv[2], &local);
++ if (result < 0) {
++ fprintf(stderr, "Invalid local address: %s\n", argv[2]);
++ return RETURN_INVALPARAM;
++ }
++
++ bool showHumanReadable = false;
++ std::vector<std::string> targetServices;
++
++ for (int i = 3; i < argc; i++) {
++ if (argv[i][0] != '-') {
++ usage();
++ return RETURN_USAGE;
++ }
++
++ switch (argv[i][1])
++ {
++ case 'p':
++ showHumanReadable = true;
++ break;
++ case 'u':
++ i++;
++
++ for ( ; i < argc && argv[i][0] == '{'; i++)
++ targetServices.push_back(argv[i]);
++
++ i--; // outer loop increments again
++ break;
++ default:
++ fprintf(stderr, "Wrong argument: %s\n", argv[i]);
++ usage();
++ return RETURN_USAGE;
++ }
++ }
++
++ std::vector<uuid_t> uuids;
++ for (std::vector<std::string>::const_iterator iter = targetServices.cbegin();
++ iter != targetServices.cend(); ++iter) {
++
++ uint128_t temp128;
++ uint16_t field1, field2, field3, field5;
++ uint32_t field0, field4;
++
++ fprintf(stderr, "Target scan for %s\n", (*iter).c_str());
++ if (sscanf((*iter).c_str(), "{%08x-%04hx-%04hx-%04hx-%08x%04hx}", &field0,
++ &field1, &field2, &field3, &field4, &field5) != 6) {
++ fprintf(stderr, "Skipping invalid uuid: %s\n", ((*iter).c_str()));
++ continue;
++ }
++
++ // we need uuid_t conversion based on
++ field0 = htonl(field0);
++ field4 = htonl(field4);
++ field1 = htons(field1);
++ field2 = htons(field2);
++ field3 = htons(field3);
++ field5 = htons(field5);
++
++ uint8_t* temp = (uint8_t*) &temp128;
++ memcpy(&temp[0], &field0, 4);
++ memcpy(&temp[4], &field1, 2);
++ memcpy(&temp[6], &field2, 2);
++ memcpy(&temp[8], &field3, 2);
++ memcpy(&temp[10], &field4, 4);
++ memcpy(&temp[14], &field5, 2);
++
++ uuid_t sdpUuid;
++ sdp_uuid128_create(&sdpUuid, &temp128);
++ uuids.push_back(sdpUuid);
++ }
++
++ sdp_session_t *session = sdp_connect( &local, &remote, SDP_RETRY_IF_BUSY);
++ if (!session) {
++ //try one more time if first time failed
++ session = sdp_connect( &local, &remote, SDP_RETRY_IF_BUSY);
++ }
++
++ if (!session) {
++ fprintf(stderr, "Cannot establish sdp session\n");
++ return RETURN_SDP_ERROR;
++ }
++
++ // set the filter for service matches
++ if (uuids.empty()) {
++ fprintf(stderr, "Using PUBLIC_BROWSE_GROUP for SDP search\n");
++ uuid_t publicBrowseGroupUuid;
++ sdp_uuid16_create(&publicBrowseGroupUuid, PUBLIC_BROWSE_GROUP);
++ uuids.push_back(publicBrowseGroupUuid);
++ }
++
++ uint32_t attributeRange = 0x0000ffff; //all attributes
++ sdp_list_t *attributes;
++ attributes = sdp_list_append(nullptr, &attributeRange);
++
++ sdp_list_t *sdpResults, *sdpIter;
++ sdp_list_t *totalResults = nullptr;
++ sdp_list_t* serviceFilter;
++
++ for (uuid_t &uuid : uuids) { // can't be const, d/t sdp_list_append signature
++ serviceFilter = sdp_list_append(nullptr, &uuid);
++ result = sdp_service_search_attr_req(session, serviceFilter,
++ SDP_ATTR_REQ_RANGE,
++ attributes, &sdpResults);
++ sdp_list_free(serviceFilter, nullptr);
++ if (result != 0) {
++ fprintf(stderr, "sdp_service_search_attr_req failed\n");
++ sdp_list_free(attributes, nullptr);
++ sdp_close(session);
++ return RETURN_SDP_ERROR;
++ }
++
++ if (!sdpResults)
++ continue;
++
++ if (!totalResults) {
++ totalResults = sdpResults;
++ sdpIter = totalResults;
++ } else {
++ // attach each new result list to the end of totalResults
++ sdpIter->next = sdpResults;
++ }
++
++ while (sdpIter->next) // skip to end of list
++ sdpIter = sdpIter->next;
++ }
++ sdp_list_free(attributes, nullptr);
++
++ // start XML generation from the front
++ sdpResults = totalResults;
++
++ QByteArray total;
++ while (sdpResults) {
++ sdp_record_t *record = (sdp_record_t *) sdpResults->data;
++
++ const QByteArray xml = parseSdpRecord(record);
++ total += xml;
++
++ sdpIter = sdpResults;
++ sdpResults = sdpResults->next;
++ free(sdpIter);
++ sdp_record_free(record);
++ }
++
++ if (!total.isEmpty()) {
++ if (showHumanReadable)
++ printf("%s", total.constData());
++ else
++ printf("%s", total.toBase64().constData());
++ }
++
++ sdp_close(session);
++
++ return RETURN_SUCCESS;
++ }