diff --git a/Source/core/StreamTypeLengthValue.h b/Source/core/StreamTypeLengthValue.h index e92bdc68d..bcfefac8f 100644 --- a/Source/core/StreamTypeLengthValue.h +++ b/Source/core/StreamTypeLengthValue.h @@ -423,10 +423,10 @@ POP_WARNING() virtual void StateChange() { } - virtual void Send(const typename DATAEXCHANGE::Request& element) + virtual void Send(const typename DATAEXCHANGE::Request& element VARIABLE_IS_NOT_USED) { } - virtual void Received(const typename DATAEXCHANGE::Request& element) + virtual void Received(const typename DATAEXCHANGE::Request& element VARIABLE_IS_NOT_USED) { } diff --git a/Source/extensions/CMakeLists.txt b/Source/extensions/CMakeLists.txt index 00ae81a12..d4709d37f 100644 --- a/Source/extensions/CMakeLists.txt +++ b/Source/extensions/CMakeLists.txt @@ -15,10 +15,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -option(LOCALTRACER - "Include local tracing support library in the build." OFF) +option(BLUETOOTH + "Include bluetooth library in the build." OFF) +option(BROADCAST + "Include broadcasting library in the build." OFF) option(HIBERNATESUPPORT "Include hibernate support in the build." OFF) +option(LOCALTRACER + "Include local tracing support library in the build." OFF) option(PRIVILEGEDREQUEST "Include hthe privileged request support in the build." OFF) option(PROCESSCONTAINERS @@ -26,14 +30,22 @@ option(PROCESSCONTAINERS option(WARNING_REPORTING "Include warning reporting in the build." OFF) -if(LOCALTRACER) - add_subdirectory(localtracer) +if(BLUETOOTH) + add_subdirectory(bluetooth) +endif() + +if(BROADCAST) + add_subdirectory(broadcast) endif() if(HIBERNATESUPPORT) add_subdirectory(hibernate) endif() +if(LOCALTRACER) + add_subdirectory(localtracer) +endif() + if(PRIVILEGEDREQUEST) add_subdirectory(privilegedrequest) endif() diff --git a/Source/extensions/bluetooth/BluetoothUtils.cpp b/Source/extensions/bluetooth/BluetoothUtils.cpp new file mode 100644 index 000000000..60d387a5b --- /dev/null +++ b/Source/extensions/bluetooth/BluetoothUtils.cpp @@ -0,0 +1,218 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BluetoothUtils.h" + +namespace Thunder { + +namespace Bluetooth { + +int BtUtilsHciForEachDevice(int flag, int (*func)(int dd, int dev_id, long arg), + long arg) +{ + struct hci_dev_list_req* dl; + struct hci_dev_req* dr; + int dev_id = -1; + int i, sk, err = 0; + + sk = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (sk < 0) + return -1; + + dl = static_cast(malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl))); + if (!dl) { + err = errno; + goto done; + } + + memset(dl, 0, HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, (void*)dl) < 0) { + err = errno; + goto free; + } + + for (i = 0; i < dl->dev_num; i++, dr++) { + if (BtUtilsHciTestBit(flag, &dr->dev_opt)) + if (!func || func(sk, dr->dev_id, arg)) { + dev_id = dr->dev_id; + break; + } + } + + if (dev_id < 0) + err = ENODEV; + +free: + free(dl); + +done: + close(sk); + errno = err; + + return dev_id; +} + +int BtUtilsHciDevInfo(int dev_id, struct hci_dev_info* di) +{ + int dd, err, ret; + + dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (dd < 0) + return dd; + + memset(di, 0, sizeof(struct hci_dev_info)); + + di->dev_id = dev_id; + ret = ioctl(dd, HCIGETDEVINFO, (void*)di); + + err = errno; + close(dd); + errno = err; + + return ret; +} + +int BtUtilsHciDevba(int dev_id, bdaddr_t* bdaddr) +{ + struct hci_dev_info di; + + memset(&di, 0, sizeof(di)); + + if (BtUtilsHciDevInfo(dev_id, &di)) + return -1; + + if (!BtUtilsHciTestBit(HCI_UP, &di.flags)) { + errno = ENETDOWN; + return -1; + } + + bacpy(bdaddr, &di.bdaddr); + + return 0; +} + +int BtUtilsOtherBdaddr(int dd, int dev_id, long arg) +{ + struct hci_dev_info di; + di.dev_id = static_cast(dev_id); + + if (ioctl(dd, HCIGETDEVINFO, (void*)&di)) + return 0; + + if (BtUtilsHciTestBit(HCI_RAW, &di.flags)) + return 0; + + return bacmp((bdaddr_t*)arg, &di.bdaddr); +} + +int BtUtilsSameBdaddr(int dd, int dev_id, long arg) +{ + struct hci_dev_info di; + di.dev_id = static_cast(dev_id); + + if (ioctl(dd, HCIGETDEVINFO, (void*)&di)) + return 0; + + return !bacmp((bdaddr_t*)arg, &di.bdaddr); +} + +int BtUtilsHciGetRoute(bdaddr_t* bdaddr) +{ + int dev_id; + bdaddr_t* bdaddr_any = static_cast(calloc(1, sizeof(*bdaddr))); + + dev_id = BtUtilsHciForEachDevice(HCI_UP, BtUtilsOtherBdaddr, + (long)(bdaddr ? bdaddr : bdaddr_any)); + if (dev_id < 0) + dev_id = BtUtilsHciForEachDevice(HCI_UP, BtUtilsSameBdaddr, + (long)(bdaddr ? bdaddr : bdaddr_any)); + + free(bdaddr_any); + + return dev_id; +} + +int BtUtilsBa2Str(const bdaddr_t* ba, char* str) +{ + return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + ba->b[5], ba->b[4], ba->b[3], ba->b[2], ba->b[1], ba->b[0]); +} + +int BtUtilsBachk(const char* str) +{ + if (!str) + return -1; + + if (strlen(str) != 17) + return -1; + + while (*str) { + if (!isxdigit(*str++)) + return -1; + + if (!isxdigit(*str++)) + return -1; + + if (*str == 0) + break; + + if (*str++ != ':') + return -1; + } + + return 0; +} + +int BtUtilsStr2Ba(const char* str, bdaddr_t* ba) +{ + int i; + + if (BtUtilsBachk(str) < 0) { + memset(ba, 0, sizeof(*ba)); + return -1; + } + + for (i = 5; i >= 0; i--, str += 3) + ba->b[i] = strtol(str, NULL, 16); + + return 0; +} + +int BtUtilsBa2Oui(const bdaddr_t* ba, char* str) +{ + return sprintf(str, "%2.2X-%2.2X-%2.2X", ba->b[5], ba->b[4], ba->b[3]); +} + +void BtUtilsBaswap(bdaddr_t* dst, const bdaddr_t* src) +{ + register unsigned char* d = (unsigned char*)dst; + register const unsigned char* s = (const unsigned char*)src; + register int i; + + for (i = 0; i < 6; i++) + d[i] = s[5 - i]; +} + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/BluetoothUtils.h b/Source/extensions/bluetooth/BluetoothUtils.h new file mode 100644 index 000000000..28ee13a8a --- /dev/null +++ b/Source/extensions/bluetooth/BluetoothUtils.h @@ -0,0 +1,61 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + +EXTERNAL int BtUtilsHciDevba(int dev_id, bdaddr_t *bdaddr); +EXTERNAL int BtUtilsHciGetRoute(bdaddr_t *bdaddr); +EXTERNAL int BtUtilsBa2Str(const bdaddr_t *ba, char *str); +EXTERNAL int BtUtilsStr2Ba(const char *str, bdaddr_t *ba); +EXTERNAL int BtUtilsBa2Oui(const bdaddr_t *ba, char *str); + +inline void BtUtilsHciFilterClear(struct hci_filter *f) +{ + memset(f, 0, sizeof(*f)); +} + +inline void BtUtilsHciSetBit(int nr, void *addr) +{ + *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31)); +} + +inline int BtUtilsHciTestBit(int nr, void *addr) +{ + return *((uint32_t *) addr + (nr >> 5)) & (1 << (nr & 31)); +} + +inline void BtUtilsHciFilterSetEvent(int e, struct hci_filter *f) +{ + BtUtilsHciSetBit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} + +inline void BtUtilsHciFilterSetPtype(int t, struct hci_filter *f) +{ + BtUtilsHciSetBit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/CMakeLists.txt b/Source/extensions/bluetooth/CMakeLists.txt new file mode 100644 index 000000000..2b44f1fd6 --- /dev/null +++ b/Source/extensions/bluetooth/CMakeLists.txt @@ -0,0 +1,130 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) + +project(${NAMESPACE}Bluetooth + VERSION 1.0.0 + DESCRIPTION "Bluetooth abstraction that communicates directly to a HCI socket" + LANGUAGES CXX) + +set(TARGET ${PROJECT_NAME}) +message("Setup ${TARGET} v${PROJECT_VERSION}") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +option(BCM43XX "Select the serial driver for bluetooth modules found on RaspberryPi's" OFF) +option(BLUETOOTH_GATT_SUPPORT "Include GATT support" OFF) +option(BLUETOOTH_AUDIO_SUPPORT "Include audio sink/source support" OFF) + +find_package(Bluez5UtilHeaders REQUIRED) + +add_library(${TARGET} + HCISocket.cpp + UUID.cpp + Definitions.cpp + BluetoothUtils.cpp + Module.cpp +) + +set(PUBLIC_HEADERS + IDriver.h + HCISocket.h + UUID.h + Debug.h + BluetoothUtils.h + Module.h + bluetooth.h +) + +if(BCM43XX) + target_sources(${TARGET} PRIVATE drivers/BCM43XX.cpp) +else() + target_sources(${TARGET} PRIVATE drivers/Basic.cpp) +endif() + +target_link_libraries(${TARGET} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}Messaging::${NAMESPACE}Messaging + Bluez5UtilHeaders::Bluez5UtilHeaders +) + +set_target_properties(${TARGET} + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + PUBLIC_HEADER "${PUBLIC_HEADERS}" # specify the public headers + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) + +target_include_directories(${TARGET} + PUBLIC + $ + $ + $ + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/drivers" +) + +target_compile_options(${TARGET} + PRIVATE + -Wno-psabi + -fdiagnostics-color=always +) + +# +# From Bluez >= v5.64 the mgmt_ltk_info struct is changed due to inclusive language changes. +# https://github.com/bluez/bluez/commit/b7d6a7d25628e9b521a29a5c133fcadcedeb2102 +# +include(CheckStructHasMember) +check_struct_has_member("struct mgmt_ltk_info" central "../include/bluetooth/bluetooth.h;../include/bluetooth/mgmt.h" NO_INCLUSIVE_LANGUAGE LANGUAGE C) + +if(${NO_INCLUSIVE_LANGUAGE}) + message(STATUS "Your version of bluez don't uses inclusive language anymore") + target_compile_definitions(${TARGET} PUBLIC NO_INCLUSIVE_LANGUAGE) +endif() + +install( + TARGETS ${TARGET} EXPORT ${TARGET}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetooth COMPONENT ${NAMESPACE}_Development + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE} +) + +InstallPackageConfig( + TARGETS ${TARGET} + DESCRIPTION "Bluetooth library" +) + +InstallCMakeConfig( + TARGETS ${TARGET} +) + +if(BLUETOOTH_GATT_SUPPORT) + add_subdirectory(gatt) +endif() + +if(BLUETOOTH_AUDIO_SUPPORT) + add_subdirectory(audio) +endif() diff --git a/Source/extensions/bluetooth/Debug.h b/Source/extensions/bluetooth/Debug.h new file mode 100644 index 000000000..b14b9249e --- /dev/null +++ b/Source/extensions/bluetooth/Debug.h @@ -0,0 +1,29 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// #define BLUETOOTH_CMD_DUMP + +#if defined(BLUETOOTH_CMD_DUMP) +#define CMD_DUMP(descr, buffer, length) \ + do { fprintf(stderr, "%s [%i]: ", descr, length); for (int i = 0; i < length; i++) { printf("%02x:", buffer[i]); } printf("\n"); } while(0) +#else +#define CMD_DUMP(descr, buffer, length) +#endif diff --git a/Source/extensions/bluetooth/Definitions.cpp b/Source/extensions/bluetooth/Definitions.cpp new file mode 100644 index 000000000..76e2550d9 --- /dev/null +++ b/Source/extensions/bluetooth/Definitions.cpp @@ -0,0 +1,117 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + + /* LMP features mapping */ + struct ConversionTable { + uint16_t id; + const TCHAR* text; + }; + + static ConversionTable lmp_features_map[] = { + /* Byte 0 */ + { LMP_3SLOT | 0x0000, _T("3-slot packets") }, /* Bit 0 */ + { LMP_5SLOT | 0x0000, _T("5-slot packets") }, /* Bit 1 */ + { LMP_ENCRYPT | 0x0000, _T("encryption") }, /* Bit 2 */ + { LMP_SOFFSET | 0x0000, _T("slot offset") }, /* Bit 3 */ + { LMP_TACCURACY | 0x0000, _T("timing accuracy") }, /* Bit 4 */ + { LMP_RSWITCH | 0x0000, _T("role switch") }, /* Bit 5 */ + { LMP_HOLD | 0x0000, _T("hold mode") }, /* Bit 6 */ + { LMP_SNIFF | 0x0000, _T("sniff mode") }, /* Bit 7 */ + + /* Byte 1 */ + { LMP_PARK | 0x0100, _T("park state") }, /* Bit 0 */ + { LMP_RSSI | 0x0100, _T("RSSI") }, /* Bit 1 */ + { LMP_QUALITY | 0x0100, _T("channel quality") }, /* Bit 2 */ + { LMP_SCO | 0x0100, _T("SCO link") }, /* Bit 3 */ + { LMP_HV2 | 0x0100, _T("HV2 packets") }, /* Bit 4 */ + { LMP_HV3 | 0x0100, _T("HV3 packets") }, /* Bit 5 */ + { LMP_ULAW | 0x0100, _T("u-law log") }, /* Bit 6 */ + { LMP_ALAW | 0x0100, _T("A-law log") }, /* Bit 7 */ + + /* Byte 2 */ + { LMP_CVSD | 0x0200, _T("CVSD") }, /* Bit 0 */ + { LMP_PSCHEME | 0x0200, _T("paging scheme") }, /* Bit 1 */ + { LMP_PCONTROL | 0x0200, _T("power control") }, /* Bit 2 */ + { LMP_TRSP_SCO | 0x0200, _T("transparent SCO") }, /* Bit 3 */ + { LMP_BCAST_ENC | 0x0200, _T("broadcast encrypt") }, /* Bit 7 */ + + /* Byte 3 */ + { LMP_EDR_ACL_2M | 0x0300, _T("EDR ACL 2 Mbps") }, /* Bit 1 */ + { LMP_EDR_ACL_3M | 0x0300, _T("EDR ACL 3 Mbps") }, /* Bit 2 */ + { LMP_ENH_ISCAN | 0x0300, _T("enhanced iscan") }, /* Bit 3 */ + { LMP_ILACE_ISCAN | 0x0300, _T("interlaced iscan") }, /* Bit 4 */ + { LMP_ILACE_PSCAN | 0x0300, _T("interlaced pscan") }, /* Bit 5 */ + { LMP_RSSI_INQ | 0x0300, _T("inquiry with RSSI") }, /* Bit 6 */ + { LMP_ESCO | 0x0300, _T("extended SCO") }, /* Bit 7 */ + + /* Byte 4 */ + { LMP_EV4 | 0x0400, _T("EV4 packets") }, /* Bit 0 */ + { LMP_EV5 | 0x0400, _T("EV5 packets") }, /* Bit 1 */ + { LMP_AFH_CAP_SLV | 0x0400, _T("AFH cap. slave") }, /* Bit 3 */ + { LMP_AFH_CLS_SLV | 0x0400, _T("AFH class. slave") }, /* Bit 4 */ + { LMP_NO_BREDR | 0x0400, _T("BR/EDR not supp.") }, /* Bit 5 */ + { LMP_LE | 0x0400, _T("LE support") }, /* Bit 6 */ + { LMP_EDR_3SLOT | 0x0400, _T("3-slot EDR ACL") }, /* Bit 7 */ + + /* Byte 5 */ + { LMP_EDR_5SLOT | 0x0500, _T("5-slot EDR ACL") }, /* Bit 0 */ + { LMP_SNIFF_SUBR | 0x0500, _T("sniff subrating") }, /* Bit 1 */ + { LMP_PAUSE_ENC | 0x0500, _T("pause encryption") }, /* Bit 2 */ + { LMP_AFH_CAP_MST | 0x0500, _T("AFH cap. master") }, /* Bit 3 */ + { LMP_AFH_CLS_MST | 0x0500, _T("AFH class. master") }, /* Bit 4 */ + { LMP_EDR_ESCO_2M | 0x0500, _T("EDR eSCO 2 Mbps") }, /* Bit 5 */ + { LMP_EDR_ESCO_3M | 0x0500, _T("EDR eSCO 3 Mbps") }, /* Bit 6 */ + { LMP_EDR_3S_ESCO | 0x0500, _T("3-slot EDR eSCO") }, /* Bit 7 */ + + /* Byte 6 */ + { LMP_EXT_INQ | 0x0600, _T("extended inquiry") }, /* Bit 0 */ + { LMP_LE_BREDR | 0x0600, _T("LE and BR/EDR") }, /* Bit 1 */ + { LMP_SIMPLE_PAIR | 0x0600, _T("simple pairing") }, /* Bit 3 */ + { LMP_ENCAPS_PDU | 0x0600, _T("encapsulated PDU") }, /* Bit 4 */ + { LMP_ERR_DAT_REP | 0x0600, _T("err. data report") }, /* Bit 5 */ + { LMP_NFLUSH_PKTS | 0x0600, _T("non-flush flag") }, /* Bit 6 */ + + /* Byte 7 */ + { LMP_LSTO | 0x0700, _T("LSTO") }, /* Bit 1 */ + { LMP_INQ_TX_PWR | 0x0700, _T("inquiry TX power") }, /* Bit 2 */ + { LMP_EPC | 0x0700, _T("EPC") }, /* Bit 2 */ + { LMP_EXT_FEAT | 0x0700, _T("extended features") }, /* Bit 7 */ + + { 0x0000, nullptr } + }; + + const TCHAR* FeatureToText(const uint16_t index) + { + ConversionTable* pos = lmp_features_map; + + while ((pos->text != nullptr) && (pos->id != index)) { + pos++; + } + + return (pos->text != nullptr ? pos->text : _T("reserved")); + } + +} } // namespace Thunder::Bluetooth + diff --git a/Source/extensions/bluetooth/HCISocket.cpp b/Source/extensions/bluetooth/HCISocket.cpp new file mode 100644 index 000000000..834bd0034 --- /dev/null +++ b/Source/extensions/bluetooth/HCISocket.cpp @@ -0,0 +1,1391 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HCISocket.h" + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(Bluetooth::Address::type) + { Bluetooth::Address::BREDR_ADDRESS, _TXT(_T("bredr")) }, + { Bluetooth::Address::LE_PUBLIC_ADDRESS, _TXT(_T("le_public")) }, + { Bluetooth::Address::LE_RANDOM_ADDRESS, _TXT(_T("le_random")) }, +ENUM_CONVERSION_END(Bluetooth::Address::type) + +namespace Bluetooth { + +uint32_t HCISocket::Advertising(const bool enable, const uint8_t mode) +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + if (enable == true) { + if (IsAdvertising() == false) { + result = Core::ERROR_BAD_REQUEST; + Command::AdvertisingParametersLE parameters; + + parameters->min_interval = htobs(0x0800); + parameters->max_interval = htobs(0x0800); + parameters->advtype = mode; + parameters->chan_map = 7; + + if (Exchange(MAX_ACTION_TIMEOUT, parameters, parameters) == Core::ERROR_NONE) { + // if ((temp == Core::ERROR_NONE) && (parameters.Response() == 0)) { + + Command::AdvertisingEnableLE advertising; + + advertising->enable = 1; + + if (Exchange(MAX_ACTION_TIMEOUT, advertising, advertising) == Core::ERROR_NONE) { // && (advertising.Response() == 0)) { + _state.SetState(static_cast(_state.GetState() | ADVERTISING)); + result = Core::ERROR_NONE; + } + } + } + } else if (IsAdvertising() == true) { + result = Core::ERROR_BAD_REQUEST; + Command::AdvertisingEnableLE advertising; + + advertising->enable = 0; + + if ((Exchange(MAX_ACTION_TIMEOUT, advertising, advertising) == Core::ERROR_NONE) && (advertising.Response() == 0)) { + _state.SetState(static_cast(_state.GetState() & (~ADVERTISING))); + result = Core::ERROR_NONE; + } + } + return (result); +} + +uint32_t HCISocket::Inquiry(const uint16_t scanTime, const bool limited) +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + _state.Lock(); + + if (((_state & ACTION_MASK) == 0) || ((_state & ACTION_MASK) == SCANNING)) { + /* The inquiry API limits the scan time to 326 seconds, so split it into mutliple inquiries if neccessary. */ + const uint16_t lapTime = 30; /* sec */ + uint16_t timeLeft = scanTime; + + Command::Inquiry inquiry; + Command::InquiryCancel inquiryCancel; + + inquiry->num_rsp = 255; + inquiry->length = 30; /* in 1.28s == 35s */ + + ASSERT(((uint32_t)inquiry->length * 128 / 100) > lapTime + 1); + + inquiry->lap[0] = (limited == true? 0x00 : 0x33); + inquiry->lap[1] = 0x8B; + inquiry->lap[2] = 0x9e; + + while (timeLeft > 0) { + if (Exchange(MAX_ACTION_TIMEOUT, inquiry, inquiry) == Core::ERROR_NONE) { + if (inquiry.Response() == 0) { + _state.SetState(static_cast(_state.GetState() | INQUIRING)); + + _state.Unlock(); + + // This is not super-precise, but it doesn't have to be. + uint16_t roundTime = (timeLeft > lapTime? lapTime : timeLeft); + if (_state.WaitState(ABORT_INQUIRING, (roundTime * 1000)) == true) { + roundTime = timeLeft; // essentially break + } + timeLeft -= roundTime; + + _state.Lock(); + + Exchange(MAX_ACTION_TIMEOUT, inquiryCancel, inquiryCancel); + _state.SetState(static_cast(_state.GetState() & (~(ABORT_INQUIRING | INQUIRING)))); + + result = Core::ERROR_NONE; + } else { + TRACE(Trace::Error, (_T("Inquiry command failed [0x%02x]"), inquiry.Response())); + result = Core::ERROR_ASYNC_FAILED; + break; + } + } else { + result = Core::ERROR_ASYNC_FAILED; + break; + } + } + } else { + TRACE_L1("Busy, controller is now inquiring or pairing"); + } + + _state.Unlock(); + + return (result); +} + +uint32_t HCISocket::AbortInquiry() +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + _state.Lock(); + + if ((_state & INQUIRING) != 0) { + _state.SetState(static_cast(_state.GetState() | ABORT_INQUIRING)); + result = Core::ERROR_NONE; + } + + _state.Unlock(); + + return (result); +} + +uint32_t HCISocket::Scan(const uint16_t scanTime, const bool limited, const bool passive) +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + _state.Lock(); + + if (((_state & ACTION_MASK) == 0) || ((_state & ACTION_MASK) == INQUIRING)) { + Command::ScanParametersLE parameters; + const uint16_t window = (limited? 0x12 : 0x10 /* 10ms */); + // Make sure window is smaller than interval, so the link layer has time for other Bluetooth operations during scanning. + parameters->type = (passive? SCAN_TYPE_PASSIVE : SCAN_TYPE_ACTIVE); + parameters->window = htobs(window); + parameters->interval = htobs(passive? (8 * window) : (4 * window)); + parameters->own_bdaddr_type = LE_PUBLIC_ADDRESS; + parameters->filter = SCAN_FILTER_POLICY_ALL; + + if (Exchange(MAX_ACTION_TIMEOUT, parameters, parameters) == Core::ERROR_NONE) { + if (parameters.Response() == 0) { + Command::ScanEnableLE scanner; + scanner->enable = 1; + scanner->filter_dup = SCAN_FILTER_DUPLICATES_ENABLE; + + if (Exchange(MAX_ACTION_TIMEOUT, scanner, scanner) == Core::ERROR_NONE) { + if (scanner.Response() == 0) { + _state.SetState(static_cast(_state.GetState() | SCANNING)); + + // Now lets wait for the scanning period.. + _state.Unlock(); + + _state.WaitState(ABORT_SCANNING, scanTime * 1000); + + _state.Lock(); + + scanner->enable = 0; + Exchange(MAX_ACTION_TIMEOUT, scanner, scanner); + + _state.SetState(static_cast(_state.GetState() & (~(ABORT_SCANNING | SCANNING)))); + result = Core::ERROR_NONE; + } else { + TRACE(Trace::Error, (_T("ScanEnableLE command failed [0x%02x]"), scanner.Response())); + result = Core::ERROR_ASYNC_FAILED; + } + } + } else { + TRACE(Trace::Error, (_T("ScanParametersLE command failed [0x%02x]"), parameters.Response())); + result = Core::ERROR_ASYNC_FAILED; + } + } + } else { + TRACE_L1("Busy, controller is now scanning or pairing"); + } + + _state.Unlock(); + + return (result); +} + +uint32_t HCISocket::AbortScan() +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + _state.Lock(); + + if ((_state & SCANNING) != 0) { + _state.SetState(static_cast(_state.GetState() | ABORT_SCANNING)); + result = Core::ERROR_NONE; + } + + _state.Unlock(); + + return (result); +} + +uint32_t HCISocket::Discovery(const bool enable) +{ + uint32_t result = Core::ERROR_ILLEGAL_STATE; + + _state.Lock(); + + if ((_state & ACTION_MASK) == 0) { + if (((enable == true) && (IsDiscovering() == true)) || ((enable == false) && (IsDiscovering() == false))) { + TRACE_L1("Target LE discovery mode already set..."); + } else { + if (enable == true) { + Command::ScanParametersLE parameters; + const uint16_t window = 0x10; // 10ms + parameters->type = SCAN_TYPE_PASSIVE; + // Make sure window is smaller than interval, so the link layer has time for other Bluetooth operations during scanning. + parameters->interval = htobs(8 * window); + parameters->window = htobs(window); + parameters->own_bdaddr_type = LE_PUBLIC_ADDRESS; + parameters->filter = SCAN_FILTER_POLICY_ALL; + + uint32_t rv = Exchange(MAX_ACTION_TIMEOUT, parameters, parameters); + if (rv == Core::ERROR_NONE) { + if (parameters.Response() == 0) { + result = Core::ERROR_NONE; + } else { + TRACE(Trace::Error, (_T("ScanParametersLE command failed [0x%02x]"), parameters.Response())); + result = Core::ERROR_ASYNC_FAILED; + } + } + } + + if ((result == Core::ERROR_NONE) || (enable == false)) { + Command::ScanEnableLE scanner; + scanner->enable = enable; + scanner->filter_dup = SCAN_FILTER_DUPLICATES_ENABLE; + + if (Exchange(MAX_ACTION_TIMEOUT, scanner, scanner) == Core::ERROR_NONE) { + if (scanner.Response() == 0) { + _state.SetState(static_cast(enable? (_state.GetState() | DISCOVERING) : (_state.GetState() & (~DISCOVERING)))); + result = Core::ERROR_NONE; + } else { + TRACE(Trace::Error, (_T("ScanEnableLE command failed [0x%02x]"), scanner.Response())); + result = Core::ERROR_ASYNC_FAILED; + } + } + } + } + } else { + TRACE_L1("Busy, controller is now scanning or pairing"); + } + + _state.Unlock(); + + return (result); +} + +uint32_t HCISocket::ReadStoredLinkKeys(const Address adr, const bool all, LinkKeys& list VARIABLE_IS_NOT_USED) +{ + Command::ReadStoredLinkKey parameters; + + ::memcpy(&(parameters->bdaddr), adr.Data(), sizeof(parameters->bdaddr)); + parameters->read_all = (all ? 0x1 : 0x0); + + return (Exchange(MAX_ACTION_TIMEOUT, parameters, parameters)); +} + +/* virtual */ void HCISocket::StateChange() +{ + Core::SynchronousChannelType::StateChange(); + if (IsOpen() == true) { + BtUtilsHciFilterClear(&_filter); + BtUtilsHciFilterSetPtype(HCI_EVENT_PKT, &_filter); + BtUtilsHciFilterSetEvent(EVT_LE_META_EVENT, &_filter); + BtUtilsHciFilterSetEvent(EVT_CMD_STATUS, &_filter); + BtUtilsHciFilterSetEvent(EVT_CMD_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_PIN_CODE_REQ, &_filter); + BtUtilsHciFilterSetEvent(EVT_LINK_KEY_REQ, &_filter); + BtUtilsHciFilterSetEvent(EVT_LINK_KEY_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_RETURN_LINK_KEYS, &_filter); + BtUtilsHciFilterSetEvent(EVT_IO_CAPABILITY_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_IO_CAPABILITY_RESPONSE, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_CONFIRM_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_PASSKEY_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_OOB_DATA_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_PASSKEY_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_KEYPRESS_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_SIMPLE_PAIRING_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_AUTH_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_NAME_REQ_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_READ_REMOTE_VERSION_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_READ_REMOTE_FEATURES_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_HOST_FEATURES_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_RESULT, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_RESULT_WITH_RSSI, &_filter); + BtUtilsHciFilterSetEvent(EVT_EXTENDED_INQUIRY_RESULT, &_filter); + BtUtilsHciFilterSetEvent(EVT_CONN_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_CONN_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_DISCONN_COMPLETE, &_filter); + + if (setsockopt(Handle(), SOL_HCI, HCI_FILTER, &_filter, sizeof(_filter)) < 0) { + TRACE(Trace::Error, (_T("Can't set HCI filter: %s (%d)"), strerror(errno), errno)); + } else { + TRACE(Trace::Information, (_T("HCI Filter set!"))); + } + } +} + +template void HCISocket::DeserializeScanResponse(const uint8_t* data) +{ + const uint8_t* segment = data; + uint8_t entries = *segment++; + + for (uint8_t loop = 0; loop < entries; loop++) { + const EVENT* info = reinterpret_cast(segment); + Update(*info); + segment += sizeof(EVENT); + } +} + +template<> void HCISocket::DeserializeScanResponse(const uint8_t* data) +{ + const uint8_t* segment = data; + uint8_t entries = *segment++; + + for (uint8_t loop = 0; loop < entries; loop++) { + const le_advertising_info* info = reinterpret_cast(segment); + Update(*info); + segment += (sizeof(le_advertising_info) + info->length); + } +} + +/* virtual */ uint16_t HCISocket::Deserialize(const uint8_t* dataFrame, const uint16_t availableData) +{ + CMD_DUMP("HCI event received", dataFrame, availableData); + + uint16_t result = 0; + const hci_event_hdr* hdr = reinterpret_cast(&(dataFrame[1])); + + if ( (availableData > sizeof(hci_event_hdr)) && (availableData > (sizeof(hci_event_hdr) + hdr->plen)) ) { + const uint8_t* ptr = reinterpret_cast(&(dataFrame[1 + sizeof(hci_event_hdr)])); + + result = 1 + sizeof(hci_event_hdr) + hdr->plen; + + // Deserialize scan response events + if ((hdr->evt == EVT_LE_META_EVENT) && (reinterpret_cast(ptr)->subevent == EVT_LE_ADVERTISING_REPORT)) { + DeserializeScanResponse(reinterpret_cast(ptr)->data); + } else if (hdr->evt == EVT_INQUIRY_RESULT) { + DeserializeScanResponse(ptr); + } else if (hdr->evt == EVT_INQUIRY_RESULT_WITH_RSSI) { + DeserializeScanResponse(ptr); + } else if (hdr->evt == EVT_EXTENDED_INQUIRY_RESULT) { + DeserializeScanResponse(ptr); + } else { + // All other events + Update(*hdr); + } + } + else { + TRACE_L1("EVT_HCI: Message too short => (hci_event_hdr)"); + } + + return (result); +} + +/* virtual */ void HCISocket::Update(const hci_event_hdr&) +{ +} + +/* virtual */ void HCISocket::Update(const inquiry_info&) +{ +} + +/* virtual */ void HCISocket::Update(const inquiry_info_with_rssi&) +{ +} + +/* virtual */ void HCISocket::Update(const extended_inquiry_info&) +{ +} + +/* virtual */ void HCISocket::Update(const le_advertising_info&) +{ +} + +void EIR::Ingest(const uint8_t buffer[], const uint16_t bufferLength) +{ + std::string store; + uint8_t offset = 0; + + while (((offset + buffer[offset]) <= bufferLength) && (buffer[offset+1] != 0)) { + const uint8_t length = (buffer[offset] - 1); + if (length == 0) { + break; + } + + const uint8_t type = buffer[offset + 1]; + const uint8_t* const data = &buffer[offset + 2]; + + if (type == EIR_NAME_SHORT) { + _shortName = std::string(reinterpret_cast(data), length); + } else if (type == EIR_NAME_COMPLETE) { + _completeName = std::string(reinterpret_cast(data), length); + } else if (type == EIR_CLASS_OF_DEV) { + _class = (data[0] | (data[1] << 8) | (data[2] << 16)); + } else if ((type == EIR_UUID16_SOME) || (type == EIR_UUID16_ALL)) { + for (uint8_t i = 0; i < (length / 2); i++) { + _UUIDs.emplace_back(btohs(*reinterpret_cast(data + (i * 2)))); + } + } else if ((type == EIR_UUID128_SOME) || (type == EIR_UUID128_ALL)) { + for (uint8_t i = 0; i < (length / 16); i++) { + _UUIDs.emplace_back(data + (i * 16)); + } + } + + offset += (1 /* length */ + 1 /* type */ + length); + } +} + +// -------------------------------------------------------------------------------------------------- +// ManagementSocket !!! +// -------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------------------------ +// Create definitions for the Management commands +// ------------------------------------------------------------------------ +// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/mgmt-api.txt +// ------------------------------------------------------------------------ + +template +class ManagementType : public Core::IOutbound, public Core::IInbound { +protected: + ManagementType(uint8_t buffer[]) : _buffer(buffer) { + } + +public: + ManagementType() = delete; + ManagementType(const ManagementType&) = delete; + ManagementType& operator=(const ManagementType&) = delete; + ManagementType(const uint16_t size, uint8_t buffer[], const uint16_t adapterIndex) + : _size(size), _buffer(buffer), _offset(_size), _error(~0), _inboundSize(0) + { + _buffer[0] = (OPCODE & 0xFF); + _buffer[1] = (OPCODE >> 8) & 0xFF; + _buffer[2] = (adapterIndex & 0xFF); + _buffer[3] = ((adapterIndex >> 8) & 0xFF); + _buffer[4] = static_cast((size - sizeof(mgmt_hdr)) & 0xFF); + _buffer[5] = static_cast((size >> 8) & 0xFF); + } + virtual ~ManagementType() + { + } + +public: + void Clear() + { + ::memset(&(_buffer[sizeof(mgmt_hdr)]), 0, _size - sizeof(mgmt_hdr)); + } + OUTBOUND* operator->() + { + return (reinterpret_cast(&(_buffer[sizeof(mgmt_hdr)]))); + } + uint16_t Result() const + { + return (_error); + } + const INBOUND& Response() + { + return (_inbound); + } + uint16_t Loaded () const + { + return (_inboundSize); + } + +private: + virtual void Reload() const override + { + _error = ~0; + _offset = 0; + _inboundSize = 0; + } + virtual uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + uint16_t result = std::min(static_cast(_size - _offset), length); + if (result > 0) { + + ::memcpy(stream, &(_buffer[_offset]), result); + _offset += result; + + CMD_DUMP("MGMT sent", stream, result); + } + return (result); + } + virtual uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + CMD_DUMP("MGMT received", stream, length); + + uint16_t result = 0; + if (length >= sizeof(mgmt_hdr)) { + + const mgmt_hdr* hdr = reinterpret_cast(stream); + uint16_t opCode = btohs(hdr->opcode); + uint16_t payload = btohs(hdr->len); + + if (opCode == MGMT_EV_CMD_STATUS) { + uint16_t len = (length - sizeof(mgmt_hdr)); + const mgmt_ev_cmd_status* data = reinterpret_cast(&(stream[sizeof(mgmt_hdr)])); + if (btohs(data->opcode) == OPCODE) { + if (len < sizeof(mgmt_ev_cmd_status)) { + TRACE_L1("MGMT_EV_CMD_STATUS: Message too short; opcode=%04X", data->opcode); + _error = Core::ERROR_GENERAL; + } else { + TRACE_L2("MGMT_EV_CMD_STATUS: opcode=0x%04X, status=%d", data->opcode, data->status); + _error = data->status; + } + result = length; + } + } + else if (opCode == MGMT_EV_CMD_COMPLETE) { + const mgmt_ev_cmd_complete* data = reinterpret_cast(&(stream[sizeof(mgmt_hdr)])); + if (btohs(data->opcode) == OPCODE) { + uint16_t len = (length - sizeof(mgmt_hdr)); + if (len < sizeof(mgmt_ev_cmd_complete)) { + TRACE_L1("MGMT_EV_CMD_COMPLETE: Message too short; opcode=%04X", data->opcode); + _error = Core::ERROR_GENERAL; + } else { + _inboundSize = std::min( + static_cast(sizeof(INBOUND)), + static_cast(payload - sizeof(mgmt_ev_cmd_complete))); + + _inboundSize = std::min(_inboundSize, static_cast(len - sizeof(mgmt_ev_cmd_complete))); + ::memcpy(reinterpret_cast(&_inbound), data->data, _inboundSize); + + TRACE_L2("MGMT_EV_CMD_COMPLETE: opcode=0x%04X, status=%d", data->opcode, data->status); + _error = data->status; + } + result = length; + } + } + } + return (result); + } + virtual state IsCompleted() const override + { + return (_error != static_cast(~0) ? state::COMPLETED : state::INPROGRESS); + } + +private: + uint16_t _size; + uint8_t* _buffer; + mutable uint16_t _offset; + mutable uint16_t _error; + mutable uint16_t _inboundSize; + INBOUND _inbound; +}; + +template +class ManagementFixedType : public ManagementType { +public: + ManagementFixedType() = delete; + ManagementFixedType(const ManagementFixedType&) = delete; + ManagementFixedType& operator=(const ManagementFixedType&) = delete; + + ManagementFixedType(const uint16_t adapterIndex) + : ManagementType (sizeof(_buffer), _buffer, adapterIndex) { + this->Clear(); + } + ~ManagementFixedType() { + } + +private: + uint8_t _buffer[sizeof(mgmt_hdr) + (std::is_same::value ? 0 : sizeof(OUTBOUND))]; +}; + +template +class ManagementListType : public ManagementType { +private: + ManagementListType(const uint16_t size, uint8_t buffer[], const uint16_t adapterIndex) + : ManagementType (size, buffer, adapterIndex), _buffer(buffer) { + } +public: + ManagementListType() = delete; + ManagementListType(const ManagementListType&) = delete; + ManagementListType& operator=(const ManagementListType&) = delete; + + ManagementListType(ManagementListType&& copy) + : ManagementType (copy._buffer){ + } + + static ManagementListType Instance(const uint16_t adapterIndex, const LISTTYPE& list) { + uint16_t listLength = (list.Entries() * LISTTYPE::Length()); + uint16_t length = sizeof(mgmt_hdr) + sizeof(OUTBOUND) + listLength; + uint8_t* buffer = new uint8_t[length]; + ASSERT(buffer != nullptr); + + ManagementListType result(length, buffer, adapterIndex); + result.Clear(); + list.Clone(listLength, &(buffer[sizeof(mgmt_hdr) + sizeof(OUTBOUND)])); + return (result); + } + + ~ManagementListType() { + if (_buffer != nullptr) { + delete [] _buffer; + } + _buffer = nullptr; + } + +private: + uint8_t* _buffer; +}; + +/* 500 ms to execute a management command. Should be enough for a kernel message exchange. */ +static uint32_t MANAGMENT_TIMEOUT = 500; + +static constexpr uint8_t DISABLE_MODE = 0x00; +static constexpr uint8_t ENABLE_MODE = 0x01; +static constexpr uint8_t LIMITED_MODE = 0x02; + +namespace Management { + + typedef ManagementFixedType Settings; + + typedef ManagementFixedType Power; + + typedef ManagementFixedType Connectable; + typedef ManagementFixedType FastConnectable; + typedef ManagementFixedType Bondable; + typedef ManagementFixedType SimplePairing; + typedef ManagementFixedType SecureConnection; + typedef ManagementFixedType HighSpeed; + typedef ManagementFixedType SecureLink; + typedef ManagementFixedType LowEnergy; + typedef ManagementFixedType Advertising; + typedef ManagementFixedType Connectable; + typedef ManagementFixedType Discoverable; + typedef ManagementFixedType PublicAddress; + + typedef ManagementFixedType DeviceName; + typedef ManagementFixedType DeviceClass; + typedef ManagementFixedType AddUUID; + typedef ManagementFixedType RemoveUUID; + + typedef ManagementFixedType AddAdvertising; + typedef ManagementFixedType RemoveAdvertising; + + // Kernel-side discovery and auto-connection + typedef ManagementFixedType StartDiscovery; + typedef ManagementFixedType StopDiscovery; + typedef ManagementFixedType Block; + typedef ManagementFixedType Unblock; + typedef ManagementFixedType AddDevice; + typedef ManagementFixedType RemoveDevice; + + typedef ManagementFixedType Pair; + typedef ManagementFixedType Unpair; + typedef ManagementFixedType PairAbort; + + typedef ManagementFixedType UserPINCodeReply; + typedef ManagementFixedType UserPINCodeNegReply; + typedef ManagementFixedType UserConfirmReply; + typedef ManagementFixedType UserConfirmNegReply; + typedef ManagementFixedType UserPasskeyReply; + typedef ManagementFixedType UserPasskeyNegReply; + + typedef ManagementFixedType Privacy; + typedef ManagementFixedType Indexes; + typedef ManagementListType LinkKeys; + typedef ManagementListType LongTermKeys; + typedef ManagementListType IdentityKeys; +} + +/* static */ void ManagementSocket::Devices(std::list& adapters) +{ + Management::Indexes message(HCI_DEV_NONE); + ManagementSocket globalPort; + + if (globalPort.Exchange(MANAGMENT_TIMEOUT, message, message) == Core::ERROR_NONE) { + if (message.Result() == Core::ERROR_NONE) { + for (uint16_t index = 0; index < message.Response()[0]; index++) { + adapters.push_back(message.Response()[index+1]); + } + } else { + TRACE_GLOBAL(Trace::Error, (_T("ReadIndexList command failed [0x%02x]"), message.Result())); + } + } +} + +uint32_t ManagementSocket::Name(const string& shortName, const string& longName) +{ + Management::DeviceName message(_deviceId); + std::string shortName2(shortName.substr(0, sizeof(message->short_name) - 1)); + std::string longName2(longName.substr(0, sizeof(message->name) - 1)); + + ::strcpy(reinterpret_cast(message->short_name), shortName2.c_str()); + ::strcpy(reinterpret_cast(message->name), longName2.c_str()); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetLocalName command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + + +// COD value us composed out of major/minor from this method and OR'd values of +// service bits from each UUID added with AddUUID() +uint32_t ManagementSocket::DeviceClass(const uint8_t major, const uint8_t minor) +{ + Management::DeviceClass message(_deviceId); + message->major = (major & 0x1F); + message->minor = (minor << 2); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetDevClass command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::AddUUID(const UUID& uuid, const uint8_t codServiceBits) +{ + ASSERT(uuid.IsValid() == true); + + Management::AddUUID message(_deviceId); + ::memcpy(message->uuid, uuid.Full(), sizeof(message->uuid)); + message->svc_hint = codServiceBits; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("AddUUID command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::RemoveUUID(const UUID& uuid) +{ + ASSERT(uuid.IsValid() == true); + + Management::RemoveUUID message(_deviceId); + ::memcpy(message->uuid, uuid.Full(), sizeof(message->uuid)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("RemoveUUID command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::AddAdvertising(uint8_t& instance, const bool limited, const bool connectable, const uint16_t duration) +{ + Management::AddAdvertising message(_deviceId); + message->instance = 1; // only one advertising at a time + message->flags = ((limited? 4 : 2) | (connectable? 1 : 0)); + message->duration = 0; + message->timeout = duration; + message->adv_data_len = 0; + message->scan_rsp_len = 0; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("AddAdvertising command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } else { + instance = message->instance; + } + + return (result); +} + +uint32_t ManagementSocket::RemoveAdvertising() +{ + Management::RemoveAdvertising message(_deviceId); + message->instance = 0; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("RemoveAdvertising command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Discoverable(const bool enabled, const bool limited, const uint16_t duration) +{ + ASSERT((enabled == true) || (duration == 0)); + ASSERT((limited == false) || (duration > 0)); + + Management::Discoverable message(_deviceId); + message->val = (enabled ? (limited? LIMITED_MODE : ENABLE_MODE) : DISABLE_MODE); + message->timeout = htobs(duration); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetDiscoverable command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Power(const bool enabled) +{ + Management::Power message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetPowered command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Connectable(const bool enabled) +{ + Management::Connectable message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetConnectable command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::FastConnectable(const bool enabled) +{ + Management::FastConnectable message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetFastConnectable command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Block(const Address& address, const Address::type type) +{ + Management::Block message(_deviceId); + message->addr.type = type; + ::memcpy(&(message->addr.bdaddr), address.Data(), sizeof(message->addr.bdaddr)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("BlockDevice command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Unblock(const Address& address, const Address::type type) +{ + Management::Unblock message(_deviceId); + message->addr.type = type; + ::memcpy(&(message->addr.bdaddr), address.Data(), sizeof(message->addr.bdaddr)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("UnblockDevice command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::AddDevice(const Address& address, const Address::type type, const autoconnmode value) +{ + Management::AddDevice message(_deviceId); + message->action = value; + message->addr.type = type; + ::memcpy(&(message->addr.bdaddr), address.Data(), sizeof(message->addr.bdaddr)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("AddDevice command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::RemoveDevice(const Address& address, const Address::type type) +{ + Management::RemoveDevice message(_deviceId); + message->addr.type = type; + ::memcpy(&(message->addr.bdaddr), address.Data(), sizeof(message->addr.bdaddr)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("RemoveDevice command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Privacy(const uint8_t mode, const uint8_t identity[16]) +{ + uint32_t result = Core::ERROR_NONE; + + ASSERT((mode == 0) || (identity != nullptr)); + + Management::Privacy message(_deviceId); + message->privacy = mode; + if ((identity != nullptr) && (mode != 0)) { + ::memcpy(message->irk, identity, sizeof(message->irk)); + } + + result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetPrivacy command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Bondable(const bool enabled) +{ + Management::Bondable message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetBondable command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Advertising(const bool enabled, const bool connectable) +{ + Management::Advertising message(_deviceId); + message->val = (enabled ? (connectable? 2 : 1) : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetAdvertising command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::SimplePairing(const bool enabled) +{ + Management::SimplePairing message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetSSP command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::HighSpeed(const bool enabled) +{ + Management::HighSpeed message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetHS command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::LowEnergy(const bool enabled) +{ + Management::LowEnergy message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetLE command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::SecureLink(const bool enabled) +{ + Management::SecureLink message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetSecureLink command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::SecureConnection(const bool enabled) +{ + Management::SecureConnection message(_deviceId); + message->val = (enabled ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetSecureConn command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::PublicAddress(const Address& address) +{ + Management::PublicAddress message(_deviceId); + ::memcpy(&(message->bdaddr), address.Data(), sizeof(message->bdaddr)); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetPublicAddress command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::LinkKey(const LinkKeys& keys, const bool debugKeys) +{ + Management::LinkKeys message = Management::LinkKeys::Instance(_deviceId, keys); + message->key_count = htobs(keys.Entries()); + message->debug_keys = (debugKeys ? ENABLE_MODE : DISABLE_MODE); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetLinkKey command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::LongTermKey(const LongTermKeys& keys) +{ + Management::LongTermKeys message = Management::LongTermKeys::Instance(_deviceId, keys); + message->key_count = htobs(keys.Entries()); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetLongTermKey command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::IdentityKey(const IdentityKeys& keys) +{ + Management::IdentityKeys message = Management::IdentityKeys::Instance(_deviceId, keys); + message->irk_count = htobs(keys.Entries()); + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, message, message); + if ((result == Core::ERROR_NONE) && (message.Result() != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("SetIdentityKey command failed [0x%02x]"), message.Result())); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Discovering(const bool on, const bool regular, const bool lowEnergy) +{ + uint32_t result = Core::ERROR_UNAVAILABLE; + uint32_t commandResult = MGMT_STATUS_FAILED; + const uint8_t mode = (regular ? 1 : 0) | (lowEnergy ? 6 : 0); + + // If you do not select any type, no use calling this method + ASSERT (mode != 0); + + if (on == true) { + Management::StartDiscovery message(_deviceId); + message->type = mode; + result = Exchange(MANAGMENT_TIMEOUT, message, message); + commandResult = message.Result(); + } else { + Management::StopDiscovery message(_deviceId); + message->type = mode; + result = Exchange(MANAGMENT_TIMEOUT, message, message); + commandResult = message.Result(); + } + + if ((result == Core::ERROR_NONE) && (commandResult != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("StartDiscovery/StopDiscovery command failed [0x%02x]"), commandResult)); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +ManagementSocket::Info ManagementSocket::Settings() const +{ + Info result; + Management::Settings message(_deviceId); + + if (const_cast(this)->Exchange(MANAGMENT_TIMEOUT, message, message) == Core::ERROR_NONE) { + result = Info(message.Response()); + } + + return (result); +} + +uint32_t ManagementSocket::Pair(const Address& remote, const Address::type type, const capabilities cap) +{ + Management::Pair command(_deviceId); + + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + command->io_cap = cap; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, command, command); + if (result == Core::ERROR_NONE) { + switch (command.Result()) { + case MGMT_STATUS_SUCCESS: + break; + case MGMT_STATUS_ALREADY_PAIRED: + result = Core::ERROR_ALREADY_CONNECTED; + break; + default: + TRACE(Trace::Error, (_T("Pair command failed [0x%02x]"), command.Result())); + result = Core::ERROR_ASYNC_FAILED; + break; + } + } else if (result == Core::ERROR_TIMEDOUT) { + // OP_PAIR does not seem to send CMD_STATUS unfortunately... + result = Core::ERROR_INPROGRESS; + } + + return (result); +} + +uint32_t ManagementSocket::Unpair(const Address& remote, const Address::type type) +{ + Management::Unpair command(_deviceId); + + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + command->disconnect = 1; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, command, command); + if (result == Core::ERROR_NONE) { + switch (command.Result()) { + case MGMT_STATUS_SUCCESS: + break; + case MGMT_STATUS_NOT_PAIRED: + result = Core::ERROR_ALREADY_RELEASED; + break; + default: + TRACE(Trace::Error, (_T("Unpair command failed [0x%02x]"), command.Result())); + result = Core::ERROR_ASYNC_FAILED; + break; + } + } + + return (result); +} + +uint32_t ManagementSocket::PairAbort(const Address& remote, const Address::type type) +{ + Management::PairAbort command(_deviceId); + + command->bdaddr = *remote.Data(); + command->type = type; + + uint32_t result = Exchange(MANAGMENT_TIMEOUT, command, command); + if (result == Core::ERROR_NONE) { + switch (command.Result()) { + case MGMT_STATUS_SUCCESS: + break; + case MGMT_STATUS_INVALID_PARAMS: + // Not currently pairing + result = Core::ERROR_ILLEGAL_STATE; + break; + default: + TRACE(Trace::Error, (_T("Pairing abort command failed [0x%02x]"), command.Result())); + result = Core::ERROR_ASYNC_FAILED; + break; + } + } else if (result == Core::ERROR_TIMEDOUT) { + // Seems we need a bit more time... but it's fine. + result = Core::ERROR_INPROGRESS; + } + + return (result); +} + +uint32_t ManagementSocket::UserPINCodeReply(const Address& remote, const Address::type type, const string& pinCode) +{ + uint32_t result = Core::ERROR_UNAVAILABLE; + uint32_t commandResult = MGMT_STATUS_FAILED; + + if (pinCode.empty() == false) { + Management::UserPINCodeReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + command->pin_len = std::min(pinCode.length(), sizeof(command->pin_code)); + ::memcpy(command->pin_code, pinCode.c_str(), command->pin_len); + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } else { + Management::UserPINCodeNegReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } + + if ((result == Core::ERROR_NONE) && (commandResult != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("UserPINCodeReply/UserPINCodeNegReply command failed [0x%02x]"), commandResult)); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::UserPasskeyReply(const Address& remote, const Address::type type, const uint32_t passkey) +{ + uint32_t result = Core::ERROR_UNAVAILABLE; + uint32_t commandResult = MGMT_STATUS_FAILED; + + if (passkey != static_cast(~0)) { + Management::UserPasskeyReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + command->passkey = passkey; + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } else { + Management::UserPasskeyNegReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } + + if ((result == Core::ERROR_NONE) && (commandResult != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("UserPassReply/UserPassNegReply command failed [0x%02x]"), commandResult)); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::UserPasskeyConfirmReply(const Address& remote, const Address::type type, const bool confirm) +{ + uint32_t result = Core::ERROR_UNAVAILABLE; + uint32_t commandResult = MGMT_STATUS_FAILED; + + if (confirm == true) { + Management::UserConfirmReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } else { + Management::UserConfirmNegReply command(_deviceId); + command->addr.bdaddr = *remote.Data(); + command->addr.type = type; + result = Exchange(MANAGMENT_TIMEOUT, command, command); + commandResult = command.Result(); + } + + if ((result == Core::ERROR_NONE) && (commandResult != MGMT_STATUS_SUCCESS)) { + TRACE(Trace::Error, (_T("UserConfirmReply/UserConfirmNegReply command failed [0x%02x]"), commandResult)); + result = Core::ERROR_ASYNC_FAILED; + } + + return (result); +} + +uint32_t ManagementSocket::Notifications(const bool enabled) +{ + uint32_t result = Core::ERROR_UNAVAILABLE; + + if (IsOpen() == true) { + BtUtilsHciFilterClear(&_filter); + + if (enabled == true) { + BtUtilsHciFilterSetPtype(HCI_EVENT_PKT, &_filter); + BtUtilsHciFilterSetEvent(EVT_CMD_STATUS, &_filter); + BtUtilsHciFilterSetEvent(EVT_CMD_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_PIN_CODE_REQ, &_filter); + BtUtilsHciFilterSetEvent(EVT_LINK_KEY_REQ, &_filter); + BtUtilsHciFilterSetEvent(EVT_LINK_KEY_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_RETURN_LINK_KEYS, &_filter); + BtUtilsHciFilterSetEvent(EVT_IO_CAPABILITY_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_IO_CAPABILITY_RESPONSE, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_CONFIRM_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_PASSKEY_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_OOB_DATA_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_USER_PASSKEY_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_KEYPRESS_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_SIMPLE_PAIRING_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_AUTH_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_NAME_REQ_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_READ_REMOTE_VERSION_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_READ_REMOTE_FEATURES_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_REMOTE_HOST_FEATURES_NOTIFY, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_RESULT, &_filter); + BtUtilsHciFilterSetEvent(EVT_INQUIRY_RESULT_WITH_RSSI, &_filter); + BtUtilsHciFilterSetEvent(EVT_EXTENDED_INQUIRY_RESULT, &_filter); + BtUtilsHciFilterSetEvent(EVT_CONN_REQUEST, &_filter); + BtUtilsHciFilterSetEvent(EVT_CONN_COMPLETE, &_filter); + BtUtilsHciFilterSetEvent(EVT_DISCONN_COMPLETE, &_filter); + } + + if (setsockopt(Handle(), SOL_HCI, HCI_FILTER, &_filter, sizeof(_filter)) < 0) { + TRACE(Trace::Error, (_T("Can't set MGMT filter: %s (%d)"), strerror(errno), errno)); + result = Core::ERROR_GENERAL; + } else { + TRACE(Trace::Information, (_T("MGMT Filter set!"))); + result = Core::ERROR_NONE; + } + } + return (result); +} + +/* virtual */ uint16_t ManagementSocket::Deserialize(const uint8_t* dataFrame, const uint16_t availableData) +{ + CMD_DUMP("MGMT event received", dataFrame, availableData); + + if (availableData >= sizeof(mgmt_hdr)) { + const mgmt_hdr* hdr = reinterpret_cast(dataFrame); + Update(*hdr); + } else { + TRACE_L1("EVT_MGMT: Message too short => (hci_event_hdr)"); + } + + return (availableData); +} + +/* virtual */ void ManagementSocket::Update(const mgmt_hdr&) +{ +} + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/HCISocket.h b/Source/extensions/bluetooth/HCISocket.h new file mode 100644 index 000000000..908c28f7c --- /dev/null +++ b/Source/extensions/bluetooth/HCISocket.h @@ -0,0 +1,1311 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "UUID.h" +#include "BluetoothUtils.h" + +namespace Thunder { + +namespace Bluetooth { + + class EXTERNAL Address { + public: + Address() + : _length(0) + { + } + Address(const uint16_t deviceId) + : _length(0) + { + if (BtUtilsHciDevba(deviceId, &_address) >= 0) { + _length = sizeof(_address); + } + } + Address(const bdaddr_t& address) + : _length(sizeof(_address)) + { + ::memcpy(&_address, &address, sizeof(_address)); + } + Address(const TCHAR address[]) + : _length(sizeof(_address)) + { + ::memset(&_address, 0, sizeof(_address)); + BtUtilsStr2Ba(address, &_address); + } + Address(const Address& address) + : _length(address._length) + { + ::memcpy(&_address, &(address._address), sizeof(_address)); + } + ~Address() + { + } + + enum type : uint8_t { + BREDR_ADDRESS = 0x00, + LE_PUBLIC_ADDRESS = 0x01, + LE_RANDOM_ADDRESS = 0x02 + }; + + public: + Address& operator=(const Address& rhs) + { + _length = rhs._length; + ::memcpy(&_address, &(rhs._address), sizeof(_address)); + return (*this); + } + bool IsValid() const + { + return (_length == sizeof(_address)); + } + static Address Default() + { + int deviceId = BtUtilsHciGetRoute(nullptr); + return ((deviceId >= 0) ? Address(static_cast(deviceId)) : Address()); + } + static Address AnyInterface() + { + static bdaddr_t g_anyAddress = { 0 }; + return (Address(g_anyAddress)); + } + static Address LocalInterface() + { + static bdaddr_t g_localAddress = { 0, 0, 0, 0xFF, 0xFF, 0xFF }; + return (Address(g_localAddress)); + } + const bdaddr_t* Data() const + { + return (IsValid() ? &_address : nullptr); + } + uint8_t Length() const + { + return (_length); + } + Core::NodeId NodeId(const uint16_t channelType) const + { + Core::NodeId result; + int deviceId = BtUtilsHciGetRoute(const_cast(Data())); + + if (deviceId >= 0) { + result = Core::NodeId(static_cast(deviceId), channelType); + } + + return (result); + } + Core::NodeId NodeId(const uint8_t addressType, const uint16_t cid, const uint16_t psm) const + { + return (Core::NodeId(_address, addressType, cid, psm)); + } + bool operator==(const Address& rhs) const + { + return ((_length == rhs._length) && (memcmp(rhs._address.b, _address.b, _length) == 0)); + } + bool operator!=(const Address& rhs) const + { + return (!operator==(rhs)); + } + void OUI(char oui[9]) const + { + BtUtilsBa2Oui(Data(), oui); + } + string ToString() const + { + static constexpr TCHAR _hexArray[] = "0123456789ABCDEF"; + string result; + + if (IsValid() == true) { + for (uint8_t index = 0; index < _length; index++) { + if (result.empty() == false) { + result += ':'; + } + result += _hexArray[(_address.b[(_length - 1) - index] >> 4) & 0x0F]; + result += _hexArray[_address.b[(_length - 1) - index] & 0x0F]; + } + } + + return (result); + } + + private: + bdaddr_t _address; + uint8_t _length; + }; + + class EXTERNAL EIR { + static constexpr uint8_t EIR_UUID16_SOME = 0x02; + static constexpr uint8_t EIR_UUID16_ALL = 0x03; + static constexpr uint8_t EIR_UUID32_SOME = 0x04; + static constexpr uint8_t EIR_UUID32_ALL = 0x05; + static constexpr uint8_t EIR_UUID128_SOME = 0x06; + static constexpr uint8_t EIR_UUID128_ALL = 0x07; + static constexpr uint8_t EIR_NAME_SHORT = 0x08; + static constexpr uint8_t EIR_NAME_COMPLETE = 0x09; + static constexpr uint8_t EIR_CLASS_OF_DEV = 0x0D; + + public: + EIR() + : _shortName() + , _completeName() + , _class(0) + , _UUIDs() + { + } + explicit EIR(const string& name, const uint32_t deviceClass = 0) + : _shortName(name) + , _completeName(name) + , _class(deviceClass) + , _UUIDs() + { + } + EIR(const uint8_t buffer[], const uint16_t bufferLength) + : EIR() + { + Ingest(buffer, bufferLength); + } + EIR(const EIR&) = default; + EIR& operator =(const EIR&) = default; + ~EIR() = default; + + public: + uint32_t Class() const + { + return _class; + } + const string& ShortName() const + { + if (_shortName.empty() == true) { + return (_completeName); + } else { + return (_shortName); + } + } + const string& CompleteName() const + { + if (_completeName.empty() == true) { + return (_shortName); + } else { + return (_completeName); + } + } + const std::list& UUIDs() const + { + return (_UUIDs); + } + + void Ingest(const uint8_t buffer[], const uint16_t bufferLength); + + private: + string _shortName; + string _completeName; + uint32_t _class; + std::list _UUIDs; + }; + + template + class EXTERNAL KeyListType { + public: + typedef KEYTYPE type; + + KeyListType() : _list() { + } + KeyListType(const KeyListType& copy) : _list(copy._list) { + } + ~KeyListType() { + } + + KeyListType& operator= (const KeyListType&) = delete; + + public: + using ConstIterator = Core::IteratorType, const KEYTYPE&, typename std::list::const_iterator>; + + static uint8_t Length() { + return (KEYTYPE::Length()); + } + void Add(const KEYTYPE& key) { + _list.push_back(key); + } + void Add(const KeyListType& keylist) { + auto index = keylist.Elements(); + while (index.Next() == true) { + Add(index.Current()); + } + } + uint8_t Entries() const { + return (_list.size()); + } + void Clear() { + _list.clear(); + } + bool IsValid() const { + auto index = Elements(); + while (index.Next() == true) { + if (index.Current().IsValid() != true) { + return (false); + } + } + return (true); + } + uint16_t Clone(const uint16_t length, uint8_t buffer[]) const { + uint16_t result = 0; + typename std::list::const_iterator index (_list.begin()); + while ( (index != _list.end()) && (result <= (length - KEYTYPE::Length())) ) { + ::memcpy(&(buffer[result]), index->Data(), KEYTYPE::Length()); + result += KEYTYPE::Length(); + index++; + } + return (result); + } + ConstIterator Elements() const { + return (ConstIterator(_list)); + } + + private: + std::list _list; + }; + + class EXTERNAL LinkKey { + public: + LinkKey() { + ::memset(&_key, 0, sizeof(_key)); + _key.addr.type = ~0; + } + LinkKey(const Address& address, const uint8_t key[16], const uint8_t pinLength, const uint8_t type) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + ::memcpy(&(_key.val), key, sizeof(_key.val)); + _key.addr.type = BDADDR_BREDR; + _key.pin_len = pinLength; + _key.type = type; + } + LinkKey(const uint8_t buffer[], const uint16_t length) { + if (length == sizeof(_key)) { + ::memcpy(&_key, buffer, sizeof(_key)); + } else { + LinkKey(); + } + } + LinkKey(const Address& address, const uint8_t address_type, const string& keyString) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + _key.addr.type = address_type; + + uint16_t length = sizeof(_key) - sizeof(_key.addr); + Core::FromString(keyString, &(reinterpret_cast(&_key)[sizeof(_key.addr)]), length, nullptr); + if (length != (sizeof(_key) - sizeof(_key.addr))) { + // Seems the value is not properly restored, invalidate the object!! + _key.pin_len = 0xFF; + } + } + LinkKey(const LinkKey& copy) { + ::memcpy(&_key, ©._key, sizeof(_key)); + } + ~LinkKey() { + } + + public: + bool IsValid() const { + return ((PinLength() <= 16) && (Type() <= 8) && (LocatorType() == Bluetooth::Address::BREDR_ADDRESS)); + } + Address Locator() const { + return (_key.addr.bdaddr); + } + uint8_t LocatorType() const { + return (_key.addr.type); + } + uint8_t PinLength() const { + return(_key.pin_len); + } + uint8_t Type() const { + return(_key.type); + } + const uint8_t* Key() const { + return (_key.val); + } + const uint8_t* Data() const { + return (reinterpret_cast(&_key)); + } + static uint8_t Length() { + return (sizeof(_key)); + } + string ToString() const { + string baseKey; + Core::ToString(&(reinterpret_cast(&_key)[sizeof(_key.addr)]), sizeof(_key) - sizeof(_key.addr), false, baseKey); + return (baseKey); + } + + private: + struct mgmt_link_key_info _key; + }; + + class EXTERNAL LongTermKey { + public: + LongTermKey() { + ::memset(&_key, 0, sizeof(_key)); + _key.addr.type = ~0; + } + LongTermKey(const Address& address, const uint8_t address_type, const uint8_t type, const uint8_t master, const uint8_t encryptionSize, + const uint16_t diversifier, const uint64_t random, const uint8_t value[16]) + { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + ::memcpy(&(_key.val), value, sizeof(_key.val)); + _key.addr.type = address_type; + _key.type = type; +#ifdef NO_INCLUSIVE_LANGUAGE + _key.central = master; +#else + _key.master = master; +#endif + _key.enc_size = encryptionSize; + _key.ediv = htobs(diversifier); // 16 bits + _key.rand = htobll(random); // 64 bits + } + LongTermKey(const uint8_t buffer[], const uint16_t length) { + if (length == sizeof(_key)) { + ::memcpy(&_key, buffer, sizeof(_key)); + } else { + LongTermKey(); + } + } + LongTermKey(const Address& address, const uint8_t address_type, const string& keyString) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + _key.addr.type = address_type; + + uint16_t length = sizeof(_key) - sizeof(_key.addr); + Core::FromString(keyString, &(reinterpret_cast(&_key)[sizeof(_key.addr)]), length, nullptr); + if (length != (sizeof(_key) - sizeof(_key.addr))) { + // Seems the value is not properly restored, invalidate the object!! + _key.enc_size = 0; + } + } + LongTermKey(const LongTermKey& copy) { + ::memcpy(&_key, ©._key, sizeof(_key)); + } + ~LongTermKey() { + } + + public: + bool IsValid() const { + return ((EncryptionSize() == sizeof(_key.val)) && (Authenticated() <= 4) && (Master() <= 1) + && ((LocatorType() == Address::LE_PUBLIC_ADDRESS) || ((LocatorType() == Address::LE_RANDOM_ADDRESS) && (_key.addr.bdaddr.b[5] & 0xc0) /* static random */))); + } + Address Locator() const { + return (_key.addr.bdaddr); + } + uint8_t LocatorType() const { + return (_key.addr.type); + } + uint8_t Master() const { +#ifdef NO_INCLUSIVE_LANGUAGE + return(_key.central); +#else + return(_key.master); +#endif + } + uint8_t Authenticated() const { + return(_key.type); + } + uint8_t EncryptionSize() const { + return(_key.enc_size); + } + uint16_t Diversifier() const { + return(btohs(_key.ediv)); + } + uint64_t Random() const { + return(btohll(_key.rand)); + } + const uint8_t* Value() const { + return (_key.val); + } + const uint8_t* Data() const { + return (reinterpret_cast(&_key)); + } + static uint8_t Length() { + return (sizeof(_key)); + } + string ToString() const { + string baseKey; + Core::ToString(&(reinterpret_cast(&_key)[sizeof(_key.addr)]), sizeof(_key) - sizeof(_key.addr), false, baseKey); + return (baseKey); + } + + private: + struct mgmt_ltk_info _key; + }; + + class EXTERNAL IdentityKey { + public: + IdentityKey() { + ::memset(&_key, 0, sizeof(_key)); + _key.addr.type = ~0; + } + IdentityKey(const Address& address, const uint8_t addressType, const uint8_t value[16]) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + ::memcpy(&(_key.val), value, sizeof(_key.val)); + _key.addr.type = addressType; + } + IdentityKey(const uint8_t buffer[], const uint16_t length) { + if (length == sizeof(_key)) { + ::memcpy(&_key, buffer, sizeof(_key)); + } else { + IdentityKey(); + } + } + IdentityKey(const Address& address, const uint8_t address_type, const string& keyString) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + _key.addr.type = address_type; + + uint16_t length = sizeof(_key) - sizeof(_key.addr); + Core::FromString(keyString, &(reinterpret_cast(&_key)[sizeof(_key.addr)]), length, nullptr); + if (length != (sizeof(_key) - sizeof(_key.addr))) { + // Seems the value is not properly restored, invalidate the object!! + _key.addr.type = 0xFF; + } + } + IdentityKey(const IdentityKey& copy) { + ::memcpy(&_key, ©._key, sizeof(_key)); + } + ~IdentityKey() { + } + + public: + bool IsValid() const { + return ((LocatorType() == Address::LE_PUBLIC_ADDRESS) || ((LocatorType() == Address::LE_RANDOM_ADDRESS) && (_key.addr.bdaddr.b[5] & 0xc0) /* static random */)); + } + Address Locator() const { + return (_key.addr.bdaddr); + } + uint8_t LocatorType() const { + return (_key.addr.type); + } + const uint8_t* Value() const { + return (_key.val); + } + const uint8_t* Data() const { + return (reinterpret_cast(&_key)); + } + static uint8_t Length() { + return (sizeof(_key)); + } + string ToString() const { + string baseKey; + Core::ToString(&(reinterpret_cast(&_key)[sizeof(_key.addr)]), sizeof(_key) - sizeof(_key.addr), false, baseKey); + return (baseKey); + } + + private: + struct mgmt_irk_info _key; + }; + + class EXTERNAL SignatureKey { + public: + SignatureKey() { + ::memset(&_key, 0, sizeof(_key)); + _key.addr.type = ~0; + } + SignatureKey(const Address& address, const uint8_t address_type, const uint8_t type, const uint8_t value[16]) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + ::memcpy(&(_key.val), value, sizeof(_key.val)); + _key.addr.type = address_type; + _key.type = type; + } + SignatureKey(const uint8_t buffer[], const uint16_t length) { + if (length == sizeof(_key)) { + ::memcpy(&_key, buffer, sizeof(_key)); + } else { + SignatureKey(); + } + } + SignatureKey(const Address& address, const uint8_t address_type, const string& keyString) { + ::memcpy(&(_key.addr.bdaddr), address.Data(), sizeof(_key.addr.bdaddr)); + _key.addr.type = address_type; + + uint16_t length = sizeof(_key) - sizeof(_key.addr); + Core::FromString(keyString, &(reinterpret_cast(&_key)[sizeof(_key.addr)]), length, nullptr); + if (length != (sizeof(_key) - sizeof(_key.addr))) { + // Seems the value is not properly restored, invalidate the object!! + _key.type = 0xFF; + } + } + SignatureKey(const SignatureKey& copy) { + ::memcpy(&_key, ©._key, sizeof(_key)); + } + ~SignatureKey() { + } + + public: + bool IsValid() const { + return ((Type() <= 3) + && ((LocatorType() == Address::LE_PUBLIC_ADDRESS) || ((LocatorType() == Address::LE_RANDOM_ADDRESS)))); + } + Address Locator() const { + return (_key.addr.bdaddr); + } + uint8_t LocatorType() const { + return (_key.addr.type); + } + uint8_t Type() const { + return(_key.type); + } + const uint8_t* Value() const { + return (_key.val); + } + const uint8_t* Data() const { + return (reinterpret_cast(&_key)); + } + static uint8_t Length() { + return (sizeof(_key)); + } + string ToString() const { + string baseKey; + Core::ToString(&(reinterpret_cast(&_key)[sizeof(_key.addr)]), sizeof(_key) - sizeof(_key.addr), false, baseKey); + return (baseKey); + } + + private: + struct mgmt_csrk_info _key; + }; + + typedef KeyListType LinkKeys; + typedef KeyListType LongTermKeys; + typedef KeyListType IdentityKeys; + typedef KeyListType SignatureKeys; + + + class EXTERNAL HCISocket : public Core::SynchronousChannelType { + private: + static constexpr int SCAN_TIMEOUT = 1000; + static constexpr uint8_t SCAN_TYPE_PASSIVE = 0x00; + static constexpr uint8_t SCAN_TYPE_ACTIVE = 0x01; + static constexpr uint8_t SCAN_FILTER_POLICY_ALL = 0x00; + static constexpr uint8_t SCAN_FILTER_DUPLICATES_DISABLE = 0x00; + static constexpr uint8_t SCAN_FILTER_DUPLICATES_ENABLE = 0x01; + static constexpr uint32_t MAX_ACTION_TIMEOUT = 2000; /* 2 Seconds for commands to complete ? */ + + public: + + template(~0)> + class CommandType : public Core::IOutbound, public Core::IInbound { + private: + CommandType& operator=(const CommandType&) = delete; + + public: + enum : uint16_t { ID = OPCODE }; + + public: + CommandType() + : _offset(sizeof(_buffer)) + , _error(~0) + { + _buffer[0] = HCI_COMMAND_PKT; + _buffer[1] = (OPCODE & 0xFF); + _buffer[2] = ((OPCODE >> 8) & 0xFF); + _buffer[3] = static_cast(sizeof(OUTBOUND)); + + ::memset(&_response, 0, sizeof(_response)); + } + CommandType(const CommandType& copy) + : _offset(copy._offset) + , _error(~0) + { + ::memcpy(_buffer, copy._buffer, sizeof(_buffer)); + ::memcpy(&_response, ©._response, sizeof(_response)); + } + virtual ~CommandType() + { + } + + public: + inline void Clear() + { + ::memset(&(_buffer[4]), 0, sizeof(_buffer) - 4); + } + inline uint32_t Result() const + { + return (_error); + } + virtual void Reload() const override + { + _offset = 0; + } + virtual uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + uint16_t result = std::min(static_cast(sizeof(_buffer) - _offset), length); + if (result > 0) { + + ::memcpy(stream, &(_buffer[_offset]), result); + _offset += result; + + CMD_DUMP("HCI sent", stream, result); + } + return (result); + } + OUTBOUND* operator->() + { + return (reinterpret_cast(&(_buffer[4]))); + } + const INBOUND& Response() const + { + return (_response); + } + + private: + virtual Core::IInbound::state IsCompleted() const override + { + return (_error != static_cast(~0) ? Core::IInbound::COMPLETED : Core::IInbound::INPROGRESS); + } + virtual uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + CMD_DUMP("HCI received", stream, length); + + uint16_t result = 0; + if (length >= (HCI_EVENT_HDR_SIZE + 1)) { + const hci_event_hdr* hdr = reinterpret_cast(&(stream[1])); + const uint8_t* ptr = reinterpret_cast(&(stream[1 + HCI_EVENT_HDR_SIZE])); + uint16_t len = (length - (1 + HCI_EVENT_HDR_SIZE)); + + if (hdr->evt == EVT_CMD_STATUS) { + const evt_cmd_status* cs = reinterpret_cast(ptr); + if (btohs(cs->opcode) == OPCODE) { + + if (cs->status == 0) { + // See if we are waiting for an event... + if (RESPONSECODE == static_cast(~0)) { + _error = 0; + } + } else { + _error = cs->status; + } + + result = length; + } + } else if (hdr->evt == EVT_CMD_COMPLETE) { + const evt_cmd_complete* cc = reinterpret_cast(ptr); + if (btohs(cc->opcode) == OPCODE) { + + if (len <= EVT_CMD_COMPLETE_SIZE) { + _error = ~0; + } else { + // See if we are waiting for an event... + if (RESPONSECODE == static_cast(~0)) { + _error = 0; + uint16_t toCopy = std::min(static_cast(sizeof(_response)), static_cast(len - EVT_CMD_COMPLETE_SIZE)); + ::memcpy(&_response, &(ptr[EVT_CMD_COMPLETE_SIZE]), toCopy); + } + } + result = length; + } + } else if ((hdr->evt == EVT_LE_META_EVENT) && (cmd_opcode_ogf(OPCODE) == OGF_LE_CTL)) { + const evt_le_meta_event* eventMetaData = reinterpret_cast(ptr); + if (eventMetaData->subevent == RESPONSECODE) { + uint16_t toCopy = std::min(static_cast(sizeof(_response)), static_cast(len - EVT_LE_META_EVENT_SIZE)); + ::memcpy(&_response, &(ptr[EVT_LE_META_EVENT_SIZE]), toCopy); + _error = 0; + result = length; + } + } else if (hdr->evt == RESPONSECODE) { + ::memcpy(&_response, ptr, std::min(static_cast(sizeof(_response)), len)); + _error = 0; + result = length; + } + } + return (result); + } + + private: + mutable uint16_t _offset; + uint8_t _buffer[1 + 3 + sizeof(OUTBOUND)]; + INBOUND _response; + uint16_t _error; + }; + + public: + class EXTERNAL FeatureIterator { + public: + FeatureIterator() + : _index(-1) + { + ::memset(_features, 0, sizeof(_features)); + } + FeatureIterator(const uint8_t length, const uint8_t data[]) + : _index(-1) + { + uint8_t copyLength = std::min(length, static_cast(sizeof(_features))); + ::memcpy(_features, data, copyLength); + if (copyLength < sizeof(_features)) { + ::memset(&_features[copyLength], 0, (sizeof(_features) - copyLength)); + } + } + FeatureIterator(const FeatureIterator& copy) + : _index(copy._index) + { + ::memcpy(_features, copy._features, sizeof(_features)); + } + ~FeatureIterator() + { + } + + public: + FeatureIterator& operator=(const FeatureIterator& rhs) + { + _index = rhs._index; + ::memcpy(_features, rhs._features, sizeof(_features)); + + return (*this); + } + + void Reset() + { + _index = -1; + } + bool IsValid() const + { + return ((_index >= 0) && (_index < static_cast(sizeof(_features) * 8))); + } + bool Next() + { + _index++; + + while ((_index < static_cast(sizeof(_features) * 8)) && ((_features[_index >> 3] & (1 << (_index & 0x7))) == 0)) { + _index++; + } + return (_index < static_cast(sizeof(_features) * 8)); + } + uint8_t Feature() const + { + return (_index); + } + const TCHAR* Text() const + { + uint16_t index = (((index & 0xF8) << 5) | (1 << (_index & 0x7))); + return (FeatureToText(index)); + } + bool HasFeatures(const uint8_t byte, uint8_t bit) const + { + return (byte < sizeof(_features) ? (_features[byte] & bit) != 0 : false); + } + + private: + const TCHAR* FeatureToText(const uint16_t index) const; + + private: + int16_t _index; + uint8_t _features[8]; + }; + + // ------------------------------------------------------------------------ + // Create definitions for the HCI commands + // ------------------------------------------------------------------------ + struct Command { +PUSH_WARNING(DISABLE_WARNING_PEDANTIC) + using Void = char[0]; +POP_WARNING() + typedef CommandType + Inquiry; + + typedef CommandType + InquiryCancel; + + typedef CommandType + Connect; + + typedef CommandType + Authenticate; + + typedef CommandType + UserConfirmReply; + + typedef CommandType + UserConfirmNegReply; + + typedef CommandType + Disconnect; + + typedef CommandType + ConnectLE; + + typedef CommandType + ConnectLECancel; + + typedef CommandType + Encrypt; + + typedef CommandType + EncryptLE; + + typedef CommandType + RemoteName; + + typedef CommandType + ScanParametersLE; + + typedef CommandType + ScanEnableLE; + + typedef CommandType + ClearWhiteList; + + typedef CommandType + ReadWhiteListSize; + + typedef CommandType + AddDeviceToWhiteList; + + typedef CommandType + RemoveDeviceFromWhiteList; + + typedef CommandType + RemoteFeaturesLE; + + typedef CommandType + AdvertisingParametersLE; + + typedef CommandType + AdvertisingEnableLE; + + typedef CommandType + ConnectionUpdate; + + typedef CommandType + ReadStoredLinkKey; + }; + + enum state : uint16_t { + IDLE = 0x0000, + SCANNING = 0x0001, + INQUIRING = 0x0002, + PAIRING = 0x0004, + DISCOVERING = 0x1000, + ADVERTISING = 0x2000, + ABORT_SCANNING = 0x4000, + ABORT_INQUIRING = 0x8000 + }; + + static constexpr uint16_t ACTION_MASK = 0x0FFF; + + public: + HCISocket(const HCISocket&) = delete; + HCISocket& operator=(const HCISocket&) = delete; + + HCISocket() + : Core::SynchronousChannelType(SocketPort::RAW, Core::NodeId(), Core::NodeId(), 1024, 1024) + , _state(IDLE) + { + } + HCISocket(const Core::NodeId& sourceNode) + : Core::SynchronousChannelType(SocketPort::RAW, sourceNode, Core::NodeId(), 1024, 1024) + , _state(IDLE) + { + } + virtual ~HCISocket() + { + Close(Core::infinite); + } + + public: + bool IsScanning() const // BLE + { + return ((_state & SCANNING) != 0); + } + bool IsInquiring() const // BR/EDR + { + return ((_state & INQUIRING) != 0); + } + bool IsAdvertising() const // BLE + { + return ((_state & ADVERTISING) != 0); + } + bool IsDiscovering() const // BR/EDR + { + return ((_state & DISCOVERING) != 0); + } + + // User-land advertising + uint32_t Advertising(const bool enable, const uint8_t mode); + + // User-land BLE background discovery + uint32_t Discovery(const bool enable); + + // BR/EDR scanning + uint32_t Inquiry(const uint16_t scanTime, const bool limited); + uint32_t AbortInquiry(); + + // BLE scanning + uint32_t Scan(const uint16_t scanTime, const bool limited, const bool passive); + uint32_t AbortScan(); + + uint32_t ReadStoredLinkKeys(const Address adr, const bool all, LinkKeys& keys); + + public: + template + void Execute(const uint32_t waitTime, const COMMAND& cmd, std::function handler) + { + class Handler : public Core::IOutbound::ICallback { + public: + Handler() = delete; + Handler(const Handler&) = delete; + Handler(const COMMAND& cmd, const std::function handler) + : _cmd(cmd) + , _handler(handler){ + } + virtual ~Handler() { + } + + public: + COMMAND& Cmd() { + return (_cmd); + } + void Updated(const Core::IOutbound& data, const uint32_t error_code) override { + //ASSERT(_cmd == data); + _handler(_cmd, error_code); + delete this; + } + + private: + COMMAND _cmd; + std::function _handler; + }; + Handler* entry = new Handler(cmd, handler); + + Send(waitTime, entry->Cmd(), entry, &(entry->Cmd())); + } + + protected: + virtual void Update(const hci_event_hdr& eventData); + virtual void Update(const inquiry_info& eventData); + virtual void Update(const inquiry_info_with_rssi& eventData); + virtual void Update(const extended_inquiry_info& eventData); + virtual void Update(const le_advertising_info& eventData); + + private: + template void DeserializeScanResponse(const uint8_t* ptr); + + private: + virtual void StateChange() override; + virtual uint16_t Deserialize(const uint8_t* dataFrame, const uint16_t availableData) override; + void SetOpcode(const uint16_t opcode); + + private: + Core::StateTrigger _state; + struct hci_filter _filter; + }; + + class EXTERNAL ManagementSocket : public Core::SynchronousChannelType { + public: + class EXTERNAL Info { + public: + class EXTERNAL Properties { + public: + Properties() : _value(0) {} + Properties(const uint32_t value) : _value(value) {} + Properties(const Properties& copy) : _value(copy._value) {} + ~Properties() {} + + Properties& operator= (const Properties& rhs) { + _value = rhs._value; + return (*this); + } + + public: + bool IsPowered() const { + return ((_value & MGMT_SETTING_POWERED) != 0); + } + bool IsConnectable() const { + return ((_value & MGMT_SETTING_CONNECTABLE) != 0); + } + bool IsFastConnectable() const { + return ((_value & MGMT_SETTING_FAST_CONNECTABLE) != 0); + } + bool IsDiscoverable() const { + return ((_value & MGMT_SETTING_DISCOVERABLE) != 0); + } + bool IsBondable() const { + return ((_value & MGMT_SETTING_BONDABLE) != 0); + } + bool HasLinkLevelSecurity() const { + return ((_value & MGMT_SETTING_LINK_SECURITY) != 0); + } + bool HasSecureSimplePairing() const { + return ((_value & MGMT_SETTING_SSP) != 0); + } + bool HasBasicEnhancedRate() const { + return ((_value & MGMT_SETTING_BREDR) != 0); + } + bool HasHighSpeed() const { + return ((_value & MGMT_SETTING_HS) != 0); + } + bool HasLowEnergy() const { + return ((_value & MGMT_SETTING_LE) != 0); + } + bool IsAdvertising() const { + return ((_value & MGMT_SETTING_ADVERTISING) != 0); + } + bool HasSecureConnections() const { + return ((_value & MGMT_SETTING_SECURE_CONN) != 0); + } + bool HasDebugKeys() const { + return ((_value & MGMT_SETTING_DEBUG_KEYS) != 0); + } + bool HasPrivacy() const { + return ((_value & MGMT_SETTING_PRIVACY) != 0); + } + bool HasConfiguration() const { + return ((_value & MGMT_SETTING_CONFIGURATION) != 0); + } + bool HasStaticAddress() const { + return ((_value & MGMT_SETTING_STATIC_ADDRESS) != 0); + } + + private: + uint32_t _value; + }; + public: + Info() + : _address() + , _version(0) + , _manufacturer(0) + , _supported(0) + , _settings(0) + , _deviceClass(0) + , _name() + , _shortName() + { + } + Info(const Info& copy) + : _address(copy._address) + , _version(copy._version) + , _manufacturer(copy._manufacturer) + , _supported(copy._supported) + , _settings(copy._settings) + , _deviceClass(copy._deviceClass) + , _name(copy._name) + , _shortName(copy._shortName) + { + } + Info(const mgmt_rp_read_info& copy) + : _address(copy.bdaddr) + , _version(copy.version) + , _manufacturer(copy.manufacturer) + , _supported(copy.supported_settings) + , _settings(copy.current_settings) + , _deviceClass((copy.dev_class[2] << 16) | (copy.dev_class[1] << 8) | copy.dev_class[0]) + , _name(Core::ToString(reinterpret_cast(copy.name))) + , _shortName(Core::ToString(reinterpret_cast(copy.short_name))) + { + } + Info& operator= (const Info& rhs) { + _address = rhs._address; + _version = rhs._version; + _manufacturer = rhs._manufacturer; + _supported = rhs._supported; + _settings = rhs._settings; + _deviceClass = rhs._deviceClass; + _name = rhs._name; + _shortName = rhs._shortName; + + return (*this); + } + + ~Info() + { + } + + public: + const Bluetooth::Address& Address() const { + return (_address); + } + uint8_t Version () const { + return (_version); + } + uint16_t Manufacturer() const { + return (_manufacturer); + } + Properties Supported () const { + return (Properties(_supported)); + } + Properties Actuals() const { + return (Properties(_settings)); + } + uint32_t DeviceClass() const { + return (_deviceClass); + } + const string& ShortName() const { + return (_shortName); + } + const string& Name() const { + return (_name); + } + + private: + Bluetooth::Address _address; + uint8_t _version; + uint16_t _manufacturer; + uint32_t _supported; + uint32_t _settings; + uint32_t _deviceClass; + string _name; + string _shortName; + }; + + enum capabilities : uint8_t { + DISPLAY_ONLY = 0x00, + DISPLAY_YES_NO = 0x01, + KEYBOARD_ONLY = 0x02, + NO_INPUT_NO_OUTPUT = 0x03, + KEYBOARD_DISPLAY = 0x04, + INVALID = 0xFF + }; + + enum autoconnmode : uint8_t { + REPORT = 0x00, + DIRECT = 0x01, // reconnect on direct advertisement + ALWAYS = 0x02 + }; + + public: + ManagementSocket(const ManagementSocket&) = delete; + ManagementSocket& operator=(const ManagementSocket&) = delete; + + ManagementSocket() + : Core::SynchronousChannelType(SocketPort::RAW, Core::NodeId(HCI_DEV_NONE, HCI_CHANNEL_CONTROL), Core::NodeId(), 1024, 1024) + , _deviceId(~0) + { + if (Core::SynchronousChannelType::Open(Core::infinite) != Core::ERROR_NONE) { + } + } + virtual ~ManagementSocket() + { + Core::SynchronousChannelType::Close(Core::infinite); + } + + public: + static void Devices(std::list& list); + + void DeviceId (const uint16_t deviceId) + { + ASSERT((_deviceId == static_cast(~0)) ^ (deviceId == static_cast(~0))); + + _deviceId = deviceId; + } + uint16_t DeviceId() const + { + return (_deviceId); + } + static bool Up(const uint16_t deviceId) + { + bool result = false; + int descriptor; + + if ((descriptor = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) { + TRACE_L1("Could not open a socket. Error: %d", errno); + } + else { + if ( (::ioctl(descriptor, HCIDEVUP, deviceId) == 0) || (errno == EALREADY) ) { + result = true; + } + else { + TRACE_L1("Could not bring up the interface [%d]. Error: %d", deviceId, errno); + } + ::close(descriptor); + } + return (result); + } + static bool Down(const uint16_t deviceId) + { + bool result = false; + int descriptor; + if ((descriptor = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) { + TRACE_L1("Could not open a socket. Error: %d", errno); + } + else { + if ( (::ioctl(descriptor, HCIDEVDOWN, deviceId) == 0) || (errno == EALREADY) ) { + result = true; + } + else { + TRACE_L1("Could not bring down the interface [%d]. Error: %d", deviceId, errno); + } + ::close(descriptor); + } + return (result); + } + + public: + Info Settings() const; + + uint32_t Power(bool enabled); + + uint32_t Bondable(bool enabled); + uint32_t Connectable(const bool enabled); + uint32_t FastConnectable(const bool enabled); + uint32_t Discoverable(const bool enabled, const bool limited = false, const uint16_t duration = 0 /* infinite */); + uint32_t Advertising(bool enabled, const bool connectable = false); + uint32_t SimplePairing(bool enabled); + uint32_t HighSpeed(bool enabled); + uint32_t LowEnergy(bool enabled); + uint32_t SecureLink(bool enabled); + uint32_t SecureConnection(bool enabled); + uint32_t PublicAddress(const Address& address); + + uint32_t Name(const string& shortName, const string& longName); + uint32_t DeviceClass(const uint8_t major, const uint8_t minor); + uint32_t AddUUID(const UUID& uuid, const uint8_t codServiceBits); + uint32_t RemoveUUID(const UUID& uuid); + + // Prefer this over Advertising(true) + uint32_t AddAdvertising(uint8_t& instance, const bool limited = false, const bool connectable = true, const uint16_t duration = 0 /* inifite */); + uint32_t RemoveAdvertising(); + + // Kernel-side BLE discovery and autoconnection + uint32_t Discovering(const bool on, const bool regular, const bool LowEnergy); + uint32_t Block(const Address& address, const Address::type type); + uint32_t Unblock(const Address& address, const Address::type type); + uint32_t AddDevice(const Address& address, const Address::type type, const autoconnmode value = REPORT); + uint32_t RemoveDevice(const Address& address, const Address::type type); + + uint32_t Pair(const Address& remote, const Address::type type, const capabilities cap = NO_INPUT_NO_OUTPUT); + uint32_t Unpair(const Address& remote, const Address::type type); + uint32_t PairAbort(const Address& remote, const Address::type type); + + uint32_t UserPINCodeReply(const Address& remote, const Address::type type, const string& pinCode); + uint32_t UserPasskeyReply(const Address& remote, const Address::type type, const uint32_t passkey); + uint32_t UserPasskeyConfirmReply(const Address& remote, const Address::type type, const bool confirm); + + uint32_t Privacy(const uint8_t mode, const uint8_t identity[16] = nullptr); + uint32_t LinkKey(const LinkKeys& keys, const bool debugKeys = false); + uint32_t LongTermKey(const LongTermKeys& keys); + uint32_t IdentityKey(const IdentityKeys& keys); + + public: + uint32_t Notifications(const bool enabled); + + protected: + virtual void Update(const mgmt_hdr& eventData); + + private: + virtual uint16_t Deserialize(const uint8_t* dataFrame, const uint16_t availableData) override; + + private: + uint16_t _deviceId; + struct hci_filter _filter; + }; + + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/IDriver.h b/Source/extensions/bluetooth/IDriver.h new file mode 100644 index 000000000..2ffa293f6 --- /dev/null +++ b/Source/extensions/bluetooth/IDriver.h @@ -0,0 +1,43 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef EXTERNAL +#ifdef _MSVC_LANG +#ifdef BLUETOOTH_EXPORTS +#define EXTERNAL __declspec(dllexport) +#else +#define EXTERNAL __declspec(dllimport) +#endif +#else +#define EXTERNAL __attribute__ ((visibility ("default"))) +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +EXTERNAL const char* construct_bluetooth_driver(const char* config); +EXTERNAL void destruct_bluetooth_driver(); + +#ifdef __cplusplus +} +#endif diff --git a/Source/extensions/bluetooth/Module.cpp b/Source/extensions/bluetooth/Module.cpp new file mode 100644 index 000000000..393d6a267 --- /dev/null +++ b/Source/extensions/bluetooth/Module.cpp @@ -0,0 +1,22 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Source/extensions/bluetooth/Module.h b/Source/extensions/bluetooth/Module.h new file mode 100644 index 000000000..499c7391b --- /dev/null +++ b/Source/extensions/bluetooth/Module.h @@ -0,0 +1,39 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME Bluetooth +#endif + +#include +#include + +#include <../include/bluetooth/bluetooth.h> +#include <../include/bluetooth/hci.h> +#include <../include/bluetooth/mgmt.h> +#include <../include/bluetooth/l2cap.h> + +#include "Debug.h" + +#if defined(__WINDOWS__) && defined(BLUETOOTH_EXPORTS) +#undef EXTERNAL +#define EXTERNAL EXTERNAL_EXPORT +#endif diff --git a/Source/extensions/bluetooth/UUID.cpp b/Source/extensions/bluetooth/UUID.cpp new file mode 100644 index 000000000..41a590fc9 --- /dev/null +++ b/Source/extensions/bluetooth/UUID.cpp @@ -0,0 +1,30 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UUID.h" + +namespace Thunder { + +namespace Bluetooth { + +/* static */ const uint8_t UUID::BASE[] = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/UUID.h b/Source/extensions/bluetooth/UUID.h new file mode 100644 index 000000000..740086d19 --- /dev/null +++ b/Source/extensions/bluetooth/UUID.h @@ -0,0 +1,215 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + + class EXTERNAL UUID { + private: + static const uint8_t BASE[]; + + public: + UUID() + { + _uuid[0] = 0; + } + UUID(const uint32_t uuid) + { + _uuid[0] = (uuid < 0x10000? 2 : 16); + ::memcpy(&(_uuid[1]), BASE, sizeof(_uuid) - 5); + _uuid[13] = (uuid & 0xFF); + _uuid[14] = (uuid >> 8) & 0xFF; + _uuid[15] = (uuid >> 16) & 0xFF; + _uuid[16] = (uuid >> 24) & 0xFF; + } + explicit UUID(const uint8_t uuid[16]) + { + ::memcpy(&(_uuid[1]), uuid, 16); + + // See if this contains the Base, cause than it can be a short... + if ((::memcmp(BASE, uuid, 12) == 0) && (uuid[14] == 0) && (uuid[15] == 0)) { + _uuid[0] = 2; + } + else { + _uuid[0] = 16; + } + } + explicit UUID(const string& uuidStr) + { + FromString(uuidStr); + } + UUID(const UUID& copy) + { + ::memcpy(_uuid, copy._uuid, sizeof(_uuid)); + } + ~UUID() + { + } + + UUID& operator=(const UUID& rhs) + { + ::memcpy(_uuid, rhs._uuid, sizeof(_uuid)); + return (*this); + } + + public: + bool IsValid() const + { + return (_uuid[0] != 0); + } + uint16_t Short() const + { + ASSERT(_uuid[0] == 2); + return ((_uuid[14] << 8) | _uuid[13]); + } + const uint8_t* Full() const + { + return (&_uuid[1]); + } + bool operator==(const UUID& rhs) const + { + return ((rhs._uuid[0] == _uuid[0]) && + ((_uuid[0] == 2) ? ((rhs._uuid[13] == _uuid[13]) && (rhs._uuid[14] == _uuid[14])) : + (::memcmp(_uuid, rhs._uuid, _uuid[0] + 1) == 0))); + } + bool operator!=(const UUID& rhs) const + { + return !(operator==(rhs)); + } + bool operator==(const uint16_t shortUuid) const + { + return ((HasShort() == true) && (Short() == shortUuid)); + } + bool operator!=(const uint16_t shortUuid) const + { + return !(operator==(shortUuid)); + } + bool operator<(const UUID& rhs) const + { + for (uint8_t i = 16; i > 0; i--) { + if (_uuid[i] != rhs._uuid[i]) { + return(_uuid[i] < rhs._uuid[i]); + } + } + return (false); + } + bool HasShort() const + { + return (_uuid[0] == 2); + } + uint8_t Length() const + { + return (_uuid[0]); + } + const uint8_t* Data() const + { + return (_uuid[0] == 2 ? &(_uuid[13]) : &(_uuid[1])); + } + string ToString(const bool full = false) const + { + static const TCHAR hexArray[] = "0123456789abcdef"; + + uint8_t index = 0; + string result; + + if ((HasShort() == false) || (full == true)) { + result.resize(36); + for (uint8_t byte = 12 + 4; byte > 12; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + result[index++] = '-'; + for (uint8_t byte = 10 + 2; byte > 10; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + result[index++] = '-'; + for (uint8_t byte = 8 + 2; byte > 8; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + result[index++] = '-'; + for (uint8_t byte = 6 + 2; byte > 6; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + result[index++] = '-'; + for (uint8_t byte = 0 + 6; byte > 0; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + } + else { + result.resize(4); + + for (uint8_t byte = 12 + 2; byte > 12; byte--) { + result[index++] = hexArray[_uuid[byte] >> 4]; + result[index++] = hexArray[_uuid[byte] & 0xF]; + } + } + return (result); + } + bool FromString(const string& uuidStr) + { + if ((uuidStr.length() == 4) || (uuidStr.length() == ((16 * 2) + 4))) { + uint8_t buf[16]; + const uint16_t size = uuidStr.length(); + uint8_t* p = (buf + sizeof(buf)); + int16_t idx = 0; + + if (size == 4) { + memcpy(buf, BASE, sizeof(buf)); + p -= 2; + } + + while (idx < size) { + if ((idx == 8) || (idx == 13) || (idx == 18) || (idx == 23)) { + if (uuidStr[idx] != '-') { + break; + } else { + idx++; + } + } else { + (*--p) = ((Core::FromHexDigits(uuidStr[idx]) << 4) | Core::FromHexDigits(uuidStr[idx + 1])); + idx += 2; + } + } + + if (idx == size) { + (*this) = UUID(buf); + return true; + } + } + + return false; + } + + private: + uint8_t _uuid[17]; + }; + +} // namespace Bluetooth + +} + diff --git a/Source/extensions/bluetooth/audio/AVDTPProfile.cpp b/Source/extensions/bluetooth/audio/AVDTPProfile.cpp new file mode 100644 index 000000000..d716ef747 --- /dev/null +++ b/Source/extensions/bluetooth/audio/AVDTPProfile.cpp @@ -0,0 +1,464 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" +#include "AVDTPProfile.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace AVDTP { + + // Client methods + // -------------------------------- + + uint32_t Client::Discover(const std::function& reportCb) const + { + uint32_t result; + std::list endpoints; + + _command.Set(Signal::AVDTP_DISCOVER); + + // First issue a DISCOVER signal. + result = Execute(_command, [&](const Payload& payload) { + // Pick up end point data... + while (payload.Available() >= 2) { + uint8_t data[2]; + payload.Pop(data[0]); + payload.Pop(data[1]); + endpoints.emplace_back(data); + } + + if (payload.Available() != 0) { + TRACE_L1("Unexpected data in payload!"); + } + }); + + if (result == Core::ERROR_NONE) { + // Then for each of the stream points discovered issue a GET_CAPABILITIES signal. + for (auto& sep : endpoints) { + _command.Set(Signal::AVDTP_GET_CAPABILITIES, sep.Id()); + + Execute(_command, [&](const Payload& payload) { + // Pick up capability data for each category listed... + while (payload.Available() >= 2) { + StreamEndPoint::Service::categorytype category{}; + uint8_t length{}; + Buffer params; + + // Capabilities are stored as {category:length:params} triplets. + payload.Pop(category); + payload.Pop(length); + + if (length > 0) { + payload.Pop(params, length); + } + + sep.Add(StreamEndPoint::capability, category, std::move(params)); + } + + if (payload.Available() != 0) { + TRACE_L1("Unexpected data in payload!"); + } + }); + + // Hand this endpoint over... + reportCb(std::move(sep)); + } + } + + return (result); + } + + uint32_t Client::SetConfiguration(StreamEndPoint& ep, const uint8_t remoteId) + { + auto cb = [&](Payload& payload) { + // For each category set the configuration data... + for (auto const& entry : ep.Configuration()) { + const StreamEndPoint::Service& service = entry.second; + + ASSERT(service.Params().size() <= 255); + + // Configuration data has same structure as capabilities. + payload.Push(service.Category()); + payload.Push(service.Params().size()); + + if (service.Params().size() > 0) { + payload.Push(service.Params()); + } + } + }; + + ASSERT(remoteId != 0); + ASSERT(ep.Id() != 0); + + _command.Set(Signal::AVDTP_SET_CONFIGURATION, remoteId, ep.Id(), cb); + return (Execute(_command)); + } + + uint32_t Client::GetConfiguration(const uint8_t remoteId, const std::function& reportCb) const + { + ASSERT(reportCb != nullptr); + ASSERT(remoteId != 0); + + _command.Set(Signal::AVDTP_GET_CONFIGURATION, remoteId); + + return (Execute(_command, [&](const Payload& payload) { + + // For each category get the configuration data... + while (payload.Available() >= 2) { + StreamEndPoint::Service::categorytype category{}; + uint8_t length{}; + Buffer data; + + payload.Pop(category); + payload.Pop(length); + + if (length > 0) { + payload.Pop(data, length); + } + + reportCb(category, std::move(data)); + } + + ASSERT(payload.Available() == 0); + })); + } + + /* private */ + uint32_t Client::Execute(AVDTP::Socket::CommandType& cmd, const Payload::Inspector& inspectorCb) const + { + uint32_t result = Core::ERROR_ASYNC_FAILED; + + ASSERT(_socket != nullptr); + + if (_socket->Exchange(AVDTP::Socket::CommunicationTimeout, cmd, cmd) == Core::ERROR_NONE) { + + if (cmd.IsAccepted() == false) { + TRACE_L1("Signal %d was rejected! [%d]", cmd.Call().Id() ,cmd.Result().Error()); + } + else { + result = Core::ERROR_NONE; + + if (inspectorCb != nullptr) { + cmd.Result().InspectPayload(inspectorCb); + } + } + } + + return (result); + } + + // Server methods + // -------------------------------- + + void Server::OnDiscover(const Handler& Reply) + { + // Lists all endpoints offered by the signalling server. + // This call cannot fail. + + Reply([&](Payload& payload) { + uint8_t id = 0; + + // Serialize all endpoints... + while (Visit(++id, [&](const StreamEndPoint& ep) { + ep.Serialize(payload); + }) == true); + + // Must be at least on endpoint configured. + // ASSERT(id > 1); + }); + } + + void Server::OnGetCapabilities(const uint8_t seid, const Handler& Reply) + { + // Retrieves basic capabilities of a particular endpoint. + + if (Visit(seid, [&](const StreamEndPoint& ep) { + ASSERT(ep.Capabilities().empty() == false); + + // Serialize this endpoints capabilities... + Reply([&](Payload& payload) { + for (auto const& entry : ep.Capabilities()) { + const StreamEndPoint::Service& service = entry.second; + + if (StreamEndPoint::Service::IsBasicCategory(service.Category()) == true) { + + ASSERT(service.Params().size() <= 255); + + payload.Push(service.Category()); + payload.Push(service.Params().size()); + + if (service.Params().size() > 0) { + payload.Push(service.Params()); + } + } + } + }); + }) == false) { + Reply(Signal::errorcode::BAD_ACP_SEID); + } + } + + void Server::OnGetAllCapabilities(const uint8_t seid, const Handler& Reply) + { + // Retrieves all capabilities of a particular endpoint. + + if (Visit(seid, [&](const StreamEndPoint& ep) { + ASSERT(ep.Capabilities().empty() == false); + + // Serialize this endpoints capabilities... + Reply([&](Payload& payload) { + for (auto const& entry : ep.Capabilities()) { + const StreamEndPoint::Service& service = entry.second; + + ASSERT(service.Params().size() <= 255); + + payload.Push(service.Category()); + payload.Push(service.Params().size()); + + if (service.Params().size() > 0) { + payload.Push(service.Params()); + } + } + }); + }) == false) { + Reply(Signal::errorcode::BAD_ACP_SEID); + } + } + + void Server::OnSetConfiguration(const Signal& signal, const Handler& Reply) + { + // Sets configuration of a particular endpoint. + // Requests the endpoint state change from IDLE to CONFIGURED. + + Signal::errorcode code = Signal::errorcode::SUCCESS; + uint8_t failedCategory = 0; + + uint8_t acpSeid = 0; // ours + uint8_t intSeid = 0; // theirs + + Payload config; + + signal.InspectPayload([&](const Payload& payload) { + if (payload.Available() >= 2) { + payload.Pop(acpSeid); + acpSeid >>= 2; + + payload.Pop(intSeid); + intSeid >>= 2; + + // The rest is the raw config data. + payload.PopAssign(config, payload.Available()); + } + else { + code = Signal::errorcode::BAD_LENGTH; + } + }); + + if (code == Signal::errorcode::SUCCESS) { + code = Signal::errorcode::BAD_ACP_SEID; + + Visit(acpSeid, [&](StreamEndPoint& ep) { + code = DeserializeConfig(config, ep, failedCategory, [](const StreamEndPoint::Service::categorytype category) { + if (StreamEndPoint::Service::IsValidCategory(category) == true) { + return (Signal::errorcode::SUCCESS); + } + else { + return (Signal::errorcode::BAD_SERV_CATEGORY); + } + }); + + if (code == Signal::errorcode::SUCCESS) { + code = ToSignalCode(ep.OnSetConfiguration(failedCategory, &Reply.Channel())); + + if (code == Signal::errorcode::SUCCESS) { + ep.RemoteId(intSeid); + } + } + }); + } + + Reply(code, failedCategory); + } + + void Server::OnReconfigure(const Signal& signal, const Handler& Reply) + { + // Sets partial configuration of a particular endpoint. + // 1) Valid only in OPENED state. + // 2) Only configuration for the "Application Service" can be changed. + + Signal::errorcode code = Signal::errorcode::SUCCESS; + uint8_t failedCategory = 0; + + uint8_t seid = 0; + + Payload config; + + signal.InspectPayload([&](const Payload& payload) { + if (payload.Available() >= 2) { + payload.Pop(seid); + seid >>= 2; + + // The rest is the raw config data. + payload.PopAssign(config, payload.Available()); + } + else { + code = Signal::errorcode::BAD_LENGTH; + } + }); + + if (code == Signal::errorcode::SUCCESS) { + code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = DeserializeConfig(config, ep, failedCategory, [](const StreamEndPoint::Service::categorytype category) { + if (StreamEndPoint::Service::IsValidCategory(category) == false) { + return (Signal::errorcode::BAD_SERV_CATEGORY); + } + else if (StreamEndPoint::Service::IsApplicationCategory(category) == false) { + return (Signal::errorcode::INVALID_CAPABILITIES); + } + else { + return (Signal::errorcode::SUCCESS); + } + }); + + if (code == Signal::errorcode::SUCCESS) { + code = ToSignalCode(ep.OnReconfigure(failedCategory)); + } + }); + } + + Reply(code, failedCategory); + } + + void Server::OnOpen(const uint8_t seid, const Handler& Reply) + { + // Requests the endpoint state change from CONFIGURED to OPENED. + // Once opened the initiator can establish a transport connection. + + Signal::errorcode code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = ToSignalCode(ep.OnOpen()); + }); + + Reply(code); + } + + void Server::OnClose(const uint8_t seid, const Handler& Reply) + { + // Requests the endpoint state change to OPENED to CONFIGURED. + + Signal::errorcode code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = ToSignalCode(ep.OnClose()); + }); + + Reply(code); + } + + void Server::OnStart(const uint8_t seid, const Handler& Reply) + { + // Requests the endpoint state change from OPENED to STARTED. + + Signal::errorcode code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = ToSignalCode(ep.OnStart()); + }); + + Reply(code, seid); + } + + void Server::OnSuspend(const uint8_t seid, const Handler& Reply) + { + // Requests the endpoint state change form STARTED to OPENED. + + Signal::errorcode code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = ToSignalCode(ep.OnSuspend()); + }); + + Reply(code, seid); + } + + void Server::OnAbort(const uint8_t seid, const Handler& Reply) + { + // Requests the endpoint state change to IDLE. + + Signal::errorcode code = Signal::errorcode::BAD_ACP_SEID; + + Visit(seid, [&](StreamEndPoint& ep) { + code = ToSignalCode(ep.OnAbort()); + }); + + Reply(code); + } + + /* private */ + Signal::errorcode Server::DeserializeConfig(const Payload& config, StreamEndPoint& ep, uint8_t& invalidCategory, + const std::function& Verify) + { + // Helper method to deserialize configuration. + // Used by SetConfigure and Reconfigure, but they accept different subset of categories. + + Signal::errorcode code = Signal::errorcode::SUCCESS; + + while (config.Available() >= 2) { + StreamEndPoint::Service::categorytype category{}; + config.Pop(category); + + code = Verify(category); + + if (code != Signal::errorcode::SUCCESS) { + TRACE_L1("Invalid category!"); + invalidCategory = category; + } + else { + Buffer buffer; + uint8_t length{}; + + config.Pop(length); + + if (length > 0) { + config.Pop(buffer, length); + } + + ep.Add(category, std::move(buffer)); + } + } + + if (config.Available() != 0) { + TRACE_L1("Unexpected data in payload!"); + code = Signal::errorcode::BAD_LENGTH; + } + + return (code); + } + +} // namespace AVDTP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/AVDTPProfile.h b/Source/extensions/bluetooth/audio/AVDTPProfile.h new file mode 100644 index 000000000..83e66d1eb --- /dev/null +++ b/Source/extensions/bluetooth/audio/AVDTPProfile.h @@ -0,0 +1,529 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "AVDTPSocket.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace AVDTP { + + class Server; + class Client; + + class EXTERNAL StreamEndPointData { + public: + class EXTERNAL Service { + public: + enum categorytype : uint8_t { + INVALID = 0, + + MEDIA_TRANSPORT = 0x01, + REPORTING = 0x02, + RECOVERY = 0x03, + CONTENT_PROTECTION = 0x04, + HEADER_COMPRESSION = 0x05, + MULTIPLEXING = 0x06, + MEDIA_CODEC = 0x07, + DELAY_REPORTING = 0x08, + }; + + public: + template + Service(const categorytype category, T&& params) + : _category(category) + , _params(std::forward(params)) + { + if ((category > DELAY_REPORTING) || (category == INVALID)) { + TRACE_L1("Invalid category %d", category); + } + } + ~Service() = default; + + public: + categorytype Category() const { + return (_category); + } + const Buffer& Params() const { + return (_params); + } + + public: + static constexpr bool IsValidCategory(const categorytype category) { + return ((category != INVALID) && (category <= DELAY_REPORTING)); + } + static constexpr bool IsBasicCategory(const categorytype category) { + // which are basic capabilities? + return ((category != INVALID) && (category < DELAY_REPORTING)); + } + static constexpr bool IsApplicationCategory(const categorytype category) { + // which are Application Service capabilities? + return ((category == MEDIA_CODEC) || (category == CONTENT_PROTECTION) || (category == DELAY_REPORTING)); + } + static constexpr bool IsTransportCategory(const categorytype category) { + // which are Transport Service capabilities? + return ((category == MEDIA_TRANSPORT) || (category == REPORTING) || (category == RECOVERY) + || (category == HEADER_COMPRESSION) || (category == MULTIPLEXING)); + } + + public: +#ifdef __DEBUG__ + string AsString() const + { + static const char *categoryLabels[] = { + "INVALID", "MEDIA_TRANSPORT", "REPORTINGS", "RECOVERY", + "CONTENT_PROTECTION", "HEADER_COMPRESSION", "MULTIPLEXING", "MEDIA_CODEC", + "DELAY_REPORTING" + }; + + return (Core::Format(_T("%02x '%s' with %d bytes <%s>)"), + _category, categoryLabels[static_cast(_category)], Params().size(), Params().ToString().c_str())); + } +#endif // __DEBUG__ + + private: + const categorytype _category; + const Buffer _params; + }; // class Service + + public: + enum septype : uint8_t { + SOURCE = 0x00, + SINK = 0x01, + }; + + enum mediatype : uint8_t { + AUDIO = 0x00, + VIDEO = 0x01, + MULTIMEDIA = 0x02 + }; + + enum statetype : uint8_t { + IDLE, + CONFIGURED, + OPENED, + STARTED, + CLOSING, + ABORTING + }; + + public: + StreamEndPointData() = delete; + StreamEndPointData(const StreamEndPointData&) = delete; + StreamEndPointData& operator=(const StreamEndPointData&) = delete; + virtual ~StreamEndPointData() = default; + + StreamEndPointData(const uint8_t id, const septype service, const mediatype media, + const std::function& fillerCb = nullptr) + : _id(id) + , _remoteId(0) + , _state(IDLE) + , _type(service) + , _mediaType(media) + , _capabilities() + , _configuration() + { + ASSERT(id != 0); + + if (fillerCb != nullptr) { + fillerCb(*this); + } + } + StreamEndPointData(StreamEndPointData&& other) + : _id(other._id) + , _remoteId(other._remoteId) + , _state(other._state) + , _type(other._type) + , _mediaType(other._mediaType) + , _capabilities(std::move(other._capabilities)) + , _configuration(std::move(other._configuration)) + { + } + StreamEndPointData(const uint8_t data[2]) + : _id(0) + , _remoteId(0) + , _state(IDLE) + , _type() + , _mediaType() + , _capabilities() + , _configuration() + { + Deserialize(data); + } + + public: + using ServiceMap = std::map; + + public: + uint32_t Id() const { + return (_id); + } + uint32_t RemoteId() const { + return (_remoteId); + } + septype Type() const { + return (_type); + } + mediatype MediaType() const { + return (_mediaType); + } + bool IsFree() const { + return (_state == IDLE); + } + statetype State() const { + return (_state); + } + const ServiceMap& Capabilities() const { + return (_capabilities); + } + ServiceMap& Capabilities() { + return (_capabilities); + } + const ServiceMap& Configuration() const { + return (_configuration); + } + ServiceMap& Configuration() { + return (_configuration); + } + + public: + struct capability_t { explicit capability_t() = default; }; + static constexpr capability_t capability = capability_t{}; // tag for selecting a proper overload + + void Add(capability_t, const Service::categorytype category) + { + _capabilities.emplace(std::piecewise_construct, + std::forward_as_tuple(category), + std::forward_as_tuple(category, Buffer())); + } + void Add(const Service::categorytype category) + { + _configuration.emplace(std::piecewise_construct, + std::forward_as_tuple(category), + std::forward_as_tuple(category, Buffer())); + } + + template + void Add(capability_t, const Service::categorytype category, T&& buffer) + { + _capabilities.emplace(std::piecewise_construct, + std::forward_as_tuple(category), + std::forward_as_tuple(category, std::forward(buffer))); + } + template + void Add(const Service::categorytype category, T&& buffer) + { + _configuration.emplace(std::piecewise_construct, + std::forward_as_tuple(category), + std::forward_as_tuple(category, std::forward(buffer))); + } + void RemoteId(const uint8_t id) + { + _remoteId = id; + } + + public: + void Serialize(Payload& payload) const + { + uint8_t octet; + octet = ((Id() << 2) | ((!IsFree()) << 1)); + payload.Push(octet); + + octet = ((_mediaType << 4) | (_type << 3)); + payload.Push(octet); + } + +#ifdef __DEBUG__ + string AsString() const + { + static const char *sepTypeLabel[] = { + "Source", "Sink" + }; + + static const char *mediaTypeLabel[] = { + "Audio", "Video", "Multimedia" + }; + + ASSERT(_type <= 1); + ASSERT(_mediaType <= 2); + + return (Core::Format(_T("Stream Endpoint SEID %02x; '%s %s' (%s)"), + Id(), mediaTypeLabel[MediaType()], sepTypeLabel[Type()], (IsFree()? "free" : "in-use"))); + } +#endif // __DEBUG__ + + protected: + statetype State(const statetype newState) + { + const statetype old = _state; + + _state = newState; + + return (old); + } + + private: + void Deserialize(const uint8_t data[2]) + { + _id = (data[0] >> 2); + _mediaType = static_cast(data[1] >> 4); + _type = static_cast(!!(data[1] & 0x08)); + _state = ((data[0] & 0x02) != 0? OPENED : IDLE); + _capabilities.clear(); + _configuration.clear(); + } + + private: + uint8_t _id; + uint8_t _remoteId; + statetype _state; + septype _type; + mediatype _mediaType; + ServiceMap _capabilities; + ServiceMap _configuration; + }; // class StreamEndPointData + + struct EXTERNAL IStreamEndPointControl { + virtual ~IStreamEndPointControl() { } + + virtual uint32_t OnSetConfiguration(uint8_t& outFailedCategory, Socket* channel) = 0; + virtual uint32_t OnReconfigure(uint8_t& outFailedCategory) = 0; + virtual uint32_t OnStart() = 0; + virtual uint32_t OnSuspend() = 0; + virtual uint32_t OnOpen() = 0; + virtual uint32_t OnClose() = 0; + virtual uint32_t OnAbort() = 0; + virtual uint32_t OnSecurityControl() = 0; + }; + + class EXTERNAL StreamEndPoint : public StreamEndPointData, + public IStreamEndPointControl { + public: + StreamEndPoint() = delete; + StreamEndPoint(const StreamEndPoint&) = delete; + StreamEndPoint& operator=(const StreamEndPoint&) = delete; + ~StreamEndPoint() override = default; + + StreamEndPoint(const uint8_t id, const septype service, const mediatype media, + const std::function& fillerCb = nullptr) + : StreamEndPointData(id, service, media, fillerCb) + { + } + }; + + class EXTERNAL Client { + public: + Client(const Client&) = delete; + Client& operator=(const Client&) = delete; + ~Client() = default; + + explicit Client(Socket* socket) + : _socket(socket) + , _command(*this) + { + } + + public: + Socket* Channel() { + return (_socket); + } + const Socket* Channel() const { + return (_socket); + } + + public: + void Channel(Socket* socket) + { + _socket = socket; + } + + public: + uint32_t Discover(const std::function& reportCb) const; + + uint32_t SetConfiguration(StreamEndPoint& ep, const uint8_t remoteId); + + uint32_t GetConfiguration(const uint8_t remoteId, const std::function& reportCb) const; + + uint32_t Start(StreamEndPoint& ep) + { + ASSERT(ep.RemoteId() != 0); + + _command.Set(Signal::AVDTP_START, ep.RemoteId()); + + return (Execute(_command)); + } + uint32_t Suspend(StreamEndPoint& ep) + { + ASSERT(ep.RemoteId() != 0); + + _command.Set(Signal::AVDTP_SUSPEND, ep.RemoteId()); + + return (Execute(_command)); + } + uint32_t Open(StreamEndPoint& ep) + { + ASSERT(ep.RemoteId() != 0); + + _command.Set(Signal::AVDTP_OPEN, ep.RemoteId()); + + return (Execute(_command)); + } + uint32_t Close(StreamEndPoint& ep) + { + ASSERT(ep.RemoteId() != 0); + + _command.Set(Signal::AVDTP_CLOSE, ep.RemoteId()); + + const uint32_t result = Execute(_command); + + return (result); + } + uint32_t Abort(StreamEndPoint& ep) + { + ASSERT(ep.RemoteId() != 0); + + _command.Set(Signal::AVDTP_ABORT, ep.RemoteId()); + + const uint32_t result = Execute(_command); + + return (result); + } + + private: + uint32_t Execute(Socket::CommandType& cmd, const Payload::Inspector& inspectorCb = nullptr) const; + + private: + Socket* _socket; + mutable AVDTP::Socket::CommandType _command; + }; // class Client + + class EXTERNAL Server { + using Handler = Socket::ResponseHandler; + + public: + Server() = default; + Server(const Server&) = delete; + Server& operator=(const Server&) = delete; + virtual ~Server() = default; + + public: + virtual bool Visit(const uint8_t id, const std::function& inspectCb) = 0; + + public: + void OnSignal(const Signal& signal, const Handler& handler) + { + switch (signal.Id()) { + case Signal::AVDTP_DISCOVER: + OnDiscover(handler); + break; + case Signal::AVDTP_GET_CAPABILITIES: + OnGetCapabilities(SEID(signal), handler); + break; + break; + case Signal::AVDTP_GET_ALL_CAPABILITIES: + OnGetAllCapabilities(SEID(signal), handler); + break; + case Signal::AVDTP_SET_CONFIGURATION: + OnSetConfiguration(signal, handler); + break; + case Signal::AVDTP_OPEN: + OnOpen(SEID(signal), handler); + break; + case Signal::AVDTP_CLOSE: + OnClose(SEID(signal), handler); + break; + case Signal::AVDTP_START: + OnStart(SEID(signal), handler); + break; + case Signal::AVDTP_SUSPEND: + OnSuspend(SEID(signal), handler); + break; + case Signal::AVDTP_ABORT: + OnAbort(SEID(signal), handler); + break; + default: + TRACE_L1("Usupported signal %d", signal.Id()); + handler(Signal::errorcode::NOT_SUPPORTED_COMMAND); + break; + } + } + + private: + void OnDiscover(const Handler&); + void OnGetCapabilities(const uint8_t seid, const Handler& handler); + void OnGetAllCapabilities(const uint8_t seid, const Handler& handler); + void OnSetConfiguration(const Signal& signal, const Handler& handler); + void OnReconfigure(const Signal& signal, const Handler& handler); + void OnOpen(const uint8_t seid, const Handler& handler); + void OnClose(const uint8_t seid, const Handler& handler); + void OnStart(const uint8_t seid, const Handler& handler); + void OnSuspend(const uint8_t seid, const Handler& handler); + void OnAbort(const uint8_t seid, const Handler& handler); + + private: + uint8_t SEID(const Signal& signal) const + { + uint8_t seid = 0; + + signal.InspectPayload([&seid](const Payload& payload) { + if (payload.Available() >= 1) { + payload.Pop(seid); + seid >>= 2; + } + }); + + return (seid); + } + Signal::errorcode ToSignalCode(const uint32_t result) + { + switch (result) + { + case Core::ERROR_NONE: + return (Signal::errorcode::SUCCESS); + case Core::ERROR_UNAVAILABLE: + return (Signal::errorcode::NOT_SUPPORTED_COMMAND); + case Core::ERROR_ALREADY_CONNECTED: + return (Signal::errorcode::SEP_IN_USE); + case Core::ERROR_ALREADY_RELEASED: + return (Signal::errorcode::SEP_NOT_IN_USE); + case Core::ERROR_BAD_REQUEST: + return (Signal::errorcode::UNSUPPORTED_CONFIGURATION); + case Core::ERROR_ILLEGAL_STATE: + return (Signal::errorcode::BAD_STATE); + default: + ASSERT(!"Undefined error"); + return (Signal::errorcode::BAD_STATE); + } + } + + private: + Signal::errorcode DeserializeConfig(const Payload& config, StreamEndPoint& ep, uint8_t& invalidCategory, + const std::function& verifyFn); + + }; // class Server + +} // namespace AVDTP + +} // namespace Bluetooth + +} \ No newline at end of file diff --git a/Source/extensions/bluetooth/audio/AVDTPSocket.cpp b/Source/extensions/bluetooth/audio/AVDTPSocket.cpp new file mode 100644 index 000000000..2335b937d --- /dev/null +++ b/Source/extensions/bluetooth/audio/AVDTPSocket.cpp @@ -0,0 +1,195 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" +#include "AVDTPSocket.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace AVDTP { + + uint16_t Signal::Serialize(uint8_t stream[], const uint16_t length) const + { + ASSERT(stream != nullptr); + + constexpr uint8_t HeaderSizeSingle = 2; + constexpr uint8_t HeaderSizeStart = 3; + constexpr uint8_t HeaderSizeContinue = 1; + + DataRecord msg(stream, length, 0); + packettype pktType{}; + + if (_payload.Length() + HeaderSizeSingle <= length) { + + if (_processedPackets == 0) { + ASSERT(_expectedPackets == 0); + + pktType = packettype::SINGLE; + _expectedPackets = 1; + } + } + else { + // Have to fragment the signal... + if (_processedPackets == 0) { + ASSERT(_expectedPackets == 0); + + _expectedPackets = ((_payload.Length() + HeaderSizeStart + (length - HeaderSizeContinue) - 1) / (length - HeaderSizeContinue)); + pktType = packettype::START; + } + else if (_processedPackets == (_expectedPackets - 1)) { + pktType = packettype::END; + } + else { + pktType = packettype::CONTINUE; + } + } + + if (_processedPackets != _expectedPackets) { + + if (_processedPackets == 0) { + TRACE_L1("AVDTP: sending %s", AsString().c_str()); + } + + msg.Push(static_cast((_label << 4) | (static_cast(pktType) << 2) | static_cast(_type))); + + if (pktType == packettype::START) { + msg.Push(_expectedPackets); + } + + if ((pktType == packettype::START) || (pktType == packettype::SINGLE)) { + msg.Push(static_cast(_id & 0x3F)); + } + + const uint16_t payloadSize = std::min((length - msg.Length()), (_payload.Length() - _offset)); + + if (payloadSize != 0) { + Payload payload((_payload.Data() + _offset), payloadSize); + msg.Push(payload); + } + + _processedPackets++; + } + + return (msg.Length()); + } + + uint16_t Signal::Deserialize(const uint8_t stream[], const uint16_t length) + { + ASSERT(stream != nullptr); + + bool truncated = false; + packettype pktType{}; + DataRecord msg(stream, length); + + if (msg.Available() >= 1) { + uint8_t octet; + msg.Pop(octet); + + pktType = static_cast((octet >> 2) & 0x3); + + if ((pktType == packettype::START) || (pktType == packettype::SINGLE)) { + _expectedPackets = 1; + _label = (octet >> 4); + _type = static_cast(octet & 0x3); + } + } + else { + truncated = true; + } + + if ((truncated == false) && (pktType == packettype::START)) { + if (msg.Available() >= 1) { + msg.Pop(_expectedPackets); + } + else { + truncated = true; + } + } + + if ((truncated == false) && ((pktType == packettype::START) || (pktType == packettype::SINGLE))) { + if (msg.Available() >= 1) { + + uint8_t octet; + msg.Pop(octet); + + _id = static_cast(octet & 0x3F); + } + else { + truncated = true; + } + } + + if (truncated == false) { + if ((_type == messagetype::RESPONSE_ACCEPT) || (_type == messagetype::COMMAND)) { + msg.Pop(_payload, msg.Available()); + _errorCode = errorcode::IN_PROGRESS; + } + } + else { + TRACE_L1("AVDTP: truncated signal data"); + _errorCode = errorcode::GENERAL_ERROR; + } + + _processedPackets++; + + if (IsComplete() == true) { + + if (IsValid() == false) { + TRACE_L1("Broken frame received"); + _errorCode = errorcode::GENERAL_ERROR; + } + else if (_type == messagetype::RESPONSE_ACCEPT) { + _errorCode = errorcode::SUCCESS; + } + else if (_type == messagetype::RESPONSE_REJECT) { + + if ((_id == AVDTP_SET_CONFIGURATION) || (_id == AVDTP_RECONFIGURE) + || (_id == AVDTP_START) || (_id == AVDTP_SUSPEND)) { + + // These signal responses also include the endpoint id that failed + uint8_t octet{}; + msg.Pop(octet); + } + + if (msg.Available() >= sizeof(_errorCode)) { + msg.Pop(_errorCode); + } + else { + TRACE_L1("AVDTP: truncated signal data"); + _errorCode = errorcode::GENERAL_ERROR; + } + } + else { + // GENERIC_REJECT or anything else unexpected + _errorCode = errorcode::GENERAL_ERROR; + } + + TRACE_L1("AVDTP: received %s; result: %d", AsString().c_str(), _errorCode); + } + + return (length); + } + +} // namespace AVDTP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/AVDTPSocket.h b/Source/extensions/bluetooth/audio/AVDTPSocket.h new file mode 100644 index 000000000..922c1b6df --- /dev/null +++ b/Source/extensions/bluetooth/audio/AVDTPSocket.h @@ -0,0 +1,693 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "DataRecord.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace AVDTP { + + static constexpr uint8_t PSM = 25; + + class EXTERNAL Payload : public DataRecordBE { + public: + using Builder = std::function; + using Inspector = std::function; + + public: + // AVDTP is big-endiand oriented + using DataRecordBE::DataRecordBE; + ~Payload() = default; + }; // class Payload + + class EXTERNAL Signal { + public: + enum signalidentifier : uint8_t { + INVALID = 0x00, + AVDTP_DISCOVER = 0x01, + AVDTP_GET_CAPABILITIES = 0x02, + AVDTP_SET_CONFIGURATION = 0x03, + AVDTP_GET_CONFIGURATION = 0x04, + AVDTP_RECONFIGURE = 0x05, + AVDTP_OPEN = 0x06, + AVDTP_START = 0x07, + AVDTP_CLOSE = 0x08, + AVDTP_SUSPEND = 0x09, + AVDTP_ABORT = 0x0A, + AVDTP_SECURITY_CONTROL = 0x0B, + AVDTP_GET_ALL_CAPABILITIES = 0x0C, + AVDTP_DELAY_REPORT = 0x0D, + END + }; + + enum class errorcode : uint8_t { + SUCCESS = 0x00, + + // Header errors + BAD_HEADER_FORMAT = 0x01, + + // Payload format errors + BAD_LENGTH = 0x11, + BAD_ACP_SEID = 0x12, + SEP_IN_USE = 0x13, + SEP_NOT_IN_USE = 0x14, + BAD_SERV_CATEGORY = 0x17, + BAD_PAYLOAD_FORMAT = 0x18, + NOT_SUPPORTED_COMMAND = 0x19, + INVALID_CAPABILITIES = 0x1A, + + // Transport service errors + BAD_RECOVERY_TYPE = 0x22, + BAD_MEDIA_TRANSPORT_FORMAT = 0x23, + BAD_RECOVERY_FORMAT = 0x25, + BAD_ROHC_FORMAT = 0x26, + BAD_CP_FORMAT = 0x27, + BAD_MULTIPLEXING_FORMAT = 0x28, + UNSUPPORTED_CONFIGURATION = 0x29, + + // Procedure errors + BAD_STATE = 0x31, + + // In-house errors + IN_PROGRESS = 0xFE, + GENERAL_ERROR = 0xFF + }; + + protected: + static constexpr uint8_t INVALID_LABEL = 0xFF; + + enum class messagetype : uint8_t { + COMMAND = 0x00, + GENERAL_REJECT = 0x01, + RESPONSE_ACCEPT = 0x02, + RESPONSE_REJECT = 0x03, + END + }; + + enum class packettype : uint8_t { + SINGLE = 0x00, + START = 0x01, + CONTINUE = 0x02, + END + }; + + public: + Signal(const Signal&) = delete; + Signal& operator=(const Signal&) = delete; + + explicit Signal(const uint16_t bufferSize = 64) + : _buffer(new uint8_t[bufferSize]) + , _payload(_buffer.get(), bufferSize, 0) + , _label(INVALID_LABEL) + , _id(INVALID) + , _type(messagetype::COMMAND) + , _errorCode(errorcode::GENERAL_ERROR) + , _expectedPackets(0) + , _processedPackets(0) + , _offset(0) + { + ASSERT(bufferSize >= 2); + ASSERT(_buffer.get() != nullptr); + } + ~Signal() = default; + + public: + uint8_t Label() const { + return (_label); + } + signalidentifier Id() const { + return (_id); + } + messagetype Type() const { + return (_type); + } + errorcode Error() const { + return (_errorCode); + } + bool IsValid() const { + return ((_label != INVALID_LABEL) && (_id != INVALID) && (_id < signalidentifier::END)); + } + bool IsComplete() const { + return (_expectedPackets == _processedPackets); + } + + public: + void InspectPayload(const Payload::Inspector& inspectCb) const + { + ASSERT(inspectCb != nullptr); + + _payload.Rewind(); + inspectCb(_payload); + } + + public: + void Clear() + { + _label = INVALID_LABEL; + _id = INVALID; + _errorCode = errorcode::GENERAL_ERROR; + _expectedPackets = 0; + _processedPackets = 0; + _payload.Clear(); + } + void Reload() const + { + _expectedPackets = 0; + _processedPackets = 0; + _payload.Rewind(); + } + + uint16_t Serialize(uint8_t stream[], const uint16_t length) const; + uint16_t Deserialize(const uint8_t stream[], const uint16_t length); + + public: + string AsString() const + { +#ifdef __DEBUG__ + // Plain lookup, these tables are unlikely to ever change... + static const char *idLabels[] = { + "INVALID", "AVDTP_DISCOVER", "AVDTP_GET_CAPABILITIES", "AVDTP_SET_CONFIGURATION", + "AVDTP_GET_CONFIGURATION", "AVDTP_RECONFIGURE", "AVDTP_OPEN", "AVDTP_START", + "AVDTP_CLOSE", "AVDTP_SUSPEND", "AVDTP_ABORT", "AVDTP_SECURITY_CONTROL", + "AVDTP_GET_ALL_CAPABILITIES", "AVDTP_DELAY_REPORT" + }; + + static const char *messageTypeLabels[] = { + "COMMAND", "GENERAL_REJECT", "RESPONSE_ACCEPT", "RESPONSE_REJECT" + }; + + ASSERT(_id < signalidentifier::END); + ASSERT(_type <= messagetype::END); + + return Core::Format("signal #%d %s '%s' (%d bytes, %d packets)", + _label, messageTypeLabels[static_cast(_type)], + idLabels[static_cast(_id)], + _payload.Length(), _expectedPackets); +#else + return (Core::Format("signal #%d type %d id %d", _label, static_cast(_type), static_cast(_id))); +#endif + } + + protected: + void Set(const uint8_t label, const signalidentifier identifier, const messagetype type) + { + _label = label; + _id = identifier; + _type = type; + _offset = 0; + _expectedPackets = 0; + _processedPackets = 0; + _errorCode = errorcode::IN_PROGRESS; + _payload.Clear(); + } + void Set(const uint8_t label, const signalidentifier identifier, const messagetype type, const Payload::Builder& buildCb) + { + Set(label, identifier, type); + + if (buildCb != nullptr) { + buildCb(_payload); + } + } + + private: + std::unique_ptr _buffer; + Payload _payload; + uint8_t _label; + signalidentifier _id; + messagetype _type; + errorcode _errorCode; + mutable uint8_t _expectedPackets; + mutable uint8_t _processedPackets; + uint16_t _offset; + }; // class Signal + + class EXTERNAL Socket : public Core::SynchronousChannelType + , private Core::IOutbound::ICallback + , private Core::WorkerPool::JobType { + + friend class Core::ThreadPool::JobType; + + public: + static constexpr uint32_t CommunicationTimeout = 1000; /* ms */ + + public: + enum channeltype { + SIGNALLING, + TRANSPORT, + REPORTING, + RECOVERY, + }; + + public: + class EXTERNAL ResponseHandler { + public: + ResponseHandler(const ResponseHandler&) = default; + ResponseHandler& operator=(const ResponseHandler&) = default; + ~ResponseHandler() = default; + + ResponseHandler(Socket& channel, + const std::function& acceptor, + const std::function& rejector) + : _channel(channel) + , _acceptor(acceptor) + , _rejector(rejector) + { + } + + public: + void operator ()(const Payload::Builder& buildCb = nullptr) const + { + _acceptor(buildCb); + } + void operator ()(const Signal::errorcode result = Signal::errorcode::SUCCESS, const uint8_t data = 0) const + { + if (result == Signal::errorcode::SUCCESS) { + _acceptor(nullptr); + } + else { + _rejector(result, data); + } + } + + public: + Socket& Channel() const { + return (_channel); + } + + private: + Socket& _channel; + std::function _acceptor; + std::function _rejector; + }; // class ResponseHandler + + public: + template + class EXTERNAL CommandType : public Core::IOutbound, public Core::IInbound { + public: + class EXTERNAL Request : public Signal { + public: + Request(const Request&) = delete; + Request& operator=(const Request&) = delete; + Request() + : Signal() + , _counter(0xF) + { + } + ~Request() = default; + + public: + using Signal::Set; + + void Set(const signalidentifier signal) + { + Set(Counter(), signal, messagetype::COMMAND); + } + void Set(const signalidentifier signal, const uint8_t acpSeid) + { + ASSERT((acpSeid > 0) && (acpSeid < 0x3F)); + + Set(Counter(), signal, messagetype::COMMAND, [&](Payload& payload) { + + payload.Push(static_cast(acpSeid << 2)); + }); + } + void Set(const signalidentifier signal, const uint8_t acpSeid, const Payload::Builder& buildCb) + { + ASSERT((acpSeid > 0) && (acpSeid < 0x3F)); + + Set(Counter(), signal, messagetype::COMMAND, [&](Payload& payload) { + + payload.Push(static_cast(acpSeid << 2)); + + buildCb(payload); + }); + } + void Set(const signalidentifier signal, const uint8_t acpSeid, const uint8_t intSeid, const Payload::Builder& buildCb) + { + ASSERT((acpSeid > 0) && (acpSeid < 0x3F)); + ASSERT((intSeid > 0) && (intSeid < 0x3F)); + + Set(Counter(), signal, messagetype::COMMAND, [&](Payload& payload) { + + payload.Push(static_cast(acpSeid << 2)); + payload.Push(static_cast(intSeid << 2)); + + buildCb(payload); + }); + } + + private: + uint8_t Counter() const + { + _counter = ((_counter + 1) & 0xF); + return (_counter); + } + + private: + mutable uint8_t _counter; + }; // class Request + + public: + class EXTERNAL Response : public Signal { + public: + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + Response() + : Signal() + { + } + ~Response() = default; + }; // class Response + + public: + CommandType(const CommandType&) = delete; + CommandType& operator=(const CommandType&) = delete; + CommandType(ADMIN& admin) + : _admin(admin) + , _request() + , _response() + { + } + ~CommandType() = default; + + public: + template + void Set(const Signal::signalidentifier signal, Args&&... args) + { + _status = ~0; + _response.Clear(); + _request.Set(signal, std::forward(args)...); + } + + public: + Request& Call() { + return (_request); + } + const Request& Call() const { + return (_request); + } + Response& Result() { + return (_response); + } + const Response& Result() const { + return (_response); + } + bool IsAccepted() const { + return (Result().Error() == Signal::errorcode::SUCCESS); + } + bool IsValid() const { + return (_request.IsValid()); + } + + private: + void Reload() const override + { + _request.Reload(); + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + Socket* channel = _admin.Channel(); + ASSERT(channel != nullptr); + + const uint16_t result = _request.Serialize(stream, std::min(channel->OutputMTU(), length)); + + CMD_DUMP("AVTDP client sent", stream, result); + + return (result); + } + uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + CMD_DUMP("AVTDP client received", stream, length); + + return (_response.Deserialize(stream, length)); + } + Core::IInbound::state IsCompleted() const override + { + return (_response.IsComplete() == true? Core::IInbound::COMPLETED : Core::IInbound::INPROGRESS); + } + + private: + ADMIN& _admin; + uint32_t _status; + Request _request; + Response _response; + }; // class Command + + public: + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + + Socket(const Core::NodeId& localNode, const Core::NodeId& remoteNode) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, localNode, remoteNode, 2048, 2048) + , Core::IOutbound::ICallback() + , Core::WorkerPool::JobType(*this) + , _adminLock() + , _request() + , _response(*this) + , _omtu(0) + , _type(SIGNALLING) + , _sync(true, true) + { + } + Socket(const SOCKET& connector, const Core::NodeId& remoteNode) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, connector, remoteNode, 2048, 2048) + , Core::IOutbound::ICallback() + , Core::WorkerPool::JobType(*this) + , _adminLock() + , _request() + , _response(*this) + , _omtu(0) + , _type(SIGNALLING) + , _sync(true, true) + { + } + ~Socket() = default; + + public: + uint16_t OutputMTU() const { + return (_omtu); + } + channeltype Type() const { + return (_type); + } + + public: + void Type(const channeltype type) + { + _type = type; + +#ifdef __DEBUG__ + VARIABLE_IS_NOT_USED static const char* labels[] = { "signalling", "transport", "reporting", "recovery" }; + ASSERT(type < RECOVERY); + TRACE_L1("AVDTP: Changed channel type to: %s", labels[type]); +#endif + } + + protected: + virtual void OnSignal(const Signal& request VARIABLE_IS_NOT_USED, const ResponseHandler& handler VARIABLE_IS_NOT_USED) + { + TRACE_L1("AVDTP: Unhandled incoming signal %d", request.Id()); + } + virtual void OnPacket(const uint8_t stream[] VARIABLE_IS_NOT_USED, const uint16_t length VARIABLE_IS_NOT_USED) + { + TRACE_L1("AVDTP:: Unhandled incoming audio packet (%d bytes)", length); + } + + private: + class EXTERNAL Request : public Signal { + public: + Request(const Request&) = delete; + Request& operator=(const Request&) = delete; + Request() + : Signal() + { + } + ~Request() = default; + }; + + private: + class EXTERNAL Response : public Signal, public Core::IOutbound { + public: + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + Response(Socket& socket) + : Signal() + , _socket(socket) + { + } + ~Response() = default; + + public: + void Reload() const override + { + Signal::Reload(); + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + const uint16_t result = Signal::Serialize(stream, std::min(_socket.OutputMTU(), length)); + + CMD_DUMP("AVTDP server sent", stream, result); + + return (result); + } + + public: + void Accept(const uint8_t label, const signalidentifier identifier, const Payload::Builder& buildCb = nullptr) + { + Set(label, identifier, messagetype::RESPONSE_ACCEPT, buildCb); + } + + public: + void Reject(const uint8_t label, const signalidentifier identifier) + { + Set(label, identifier, messagetype::GENERAL_REJECT); + } + void Reject(const uint8_t label, const signalidentifier identifier, const errorcode code, const uint8_t data = 0) + { + ASSERT(code != Signal::errorcode::SUCCESS); + + if ((identifier == AVDTP_SET_CONFIGURATION) || (identifier == AVDTP_RECONFIGURE) + || (identifier == AVDTP_START) || (identifier == AVDTP_SUSPEND)) { + + Set(label, identifier, messagetype::RESPONSE_REJECT, [&](Payload& buffer) { + // Data may be zero if it doesn't fit the error code. + buffer.Push(data); + buffer.Push(code); + }); + } + else { + Set(label, identifier, messagetype::RESPONSE_REJECT, [&](Payload& buffer) { + buffer.Push(code); + }); + } + } + + private: + Socket& _socket; + }; + + private: + virtual void Operational(const bool upAndRunning) = 0; + + void StateChange() override + { + Core::SynchronousChannelType::StateChange(); + + if (IsOpen() == true) { + struct l2cap_options options{}; + socklen_t len = sizeof(options); + + ::getsockopt(Handle(), SOL_L2CAP, L2CAP_OPTIONS, &options, &len); + + ASSERT(options.omtu <= SendBufferSize()); + ASSERT(options.imtu <= ReceiveBufferSize()); + + _omtu = options.omtu; + + TRACE(Trace::Information, (_T("AVDTP channel input MTU: %d, output MTU: %d"), options.imtu, options.omtu)); + + Operational(true); + } + else { + Operational(false); + } + } + + private: + uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + uint16_t result = 0; + + if (_type == SIGNALLING) { + // This is an AVDTP request from a client. + CMD_DUMP("AVDTP server received", stream, length); + + _sync.Lock(); + _sync.ResetEvent(); + + result = _request.Deserialize(stream, length); + + if (_request.IsComplete() == true) { + JobType::Submit(); + } + } + else if (_type == TRANSPORT) { + OnPacket(stream, length); + result = length; + } + else { + ASSERT(!"Invalid socket type"); + } + + return (result); + } + + private: + void Dispatch() + { + if (_request.IsValid() == true) { + OnSignal(_request, ResponseHandler(*this, + [&](const Payload::Builder& buildCb) { + TRACE_L1("AVDTP: accepting %s", _request.AsString().c_str()); + _response.Accept(_request.Label(), _request.Id(), buildCb); + }, + [&](const Signal::errorcode result, const uint8_t data) { + TRACE_L1("AVDTP: rejecting %s, reason: %d, data 0x%02x", _request.AsString().c_str(), result, data); + _response.Reject(_request.Label(), _request.Id(), result, data); + })); + } + else { + // Totally no clue what this signal is, reply with general reject. + TRACE_L1("AVDTP: unknown signal received [%02x]", _request.Id()); + _response.Reject(_request.Label(), _request.Id()); + } + + // Clear for next request. + _request.Clear(); + + Send(CommunicationTimeout, _response, this, nullptr); + } + + private: + // IOutbound::ICallback overrides + void Updated(const Core::IOutbound& data VARIABLE_IS_NOT_USED, const uint32_t error_code VARIABLE_IS_NOT_USED) override + { + _sync.SetEvent(); + } + + private: + Core::CriticalSection _adminLock; + Request _request; + Response _response; + uint16_t _omtu; + channeltype _type; + Core::Event _sync; + }; // class Socket + +} // namespace AVDTP + +} // namespace Bluetooth + +} + diff --git a/Source/extensions/bluetooth/audio/CMakeLists.txt b/Source/extensions/bluetooth/audio/CMakeLists.txt new file mode 100644 index 000000000..d6e4e29f5 --- /dev/null +++ b/Source/extensions/bluetooth/audio/CMakeLists.txt @@ -0,0 +1,123 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2023 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) + +project(${NAMESPACE}BluetoothAudio + VERSION 1.0.0 + DESCRIPTION "Bluetooth audio library" + LANGUAGES CXX) + +set(TARGET ${PROJECT_NAME}) +message("Setup ${TARGET} v${PROJECT_VERSION}") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +find_package(SBC REQUIRED) + +file(GLOB CODEC_HEADERS codecs/*.h) + +set(PUBLIC_HEADERS + IAudioCodec.h + IAudioContentProtection.h + SDPSocket.h + SDPProfile.h + AVDTPSocket.h + AVDTPProfile.h + RTPSocket.h + DataRecord.h + Module.h + bluetooth_audio.h +) + +add_library(${TARGET} + SDPSocket.cpp + SDPProfile.cpp + AVDTPSocket.cpp + AVDTPProfile.cpp + codecs/SBC.cpp + Module.cpp +) + +target_link_libraries(${TARGET} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}Messaging::${NAMESPACE}Messaging + SBC::SBC +) + +set_target_properties(${TARGET} + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + PUBLIC_HEADER "${PUBLIC_HEADERS}" # specify the public headers + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) + +target_include_directories(${TARGET} + PUBLIC + $ + $ + $ + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/drivers" +) + +target_compile_options(${TARGET} + PRIVATE + -Wno-psabi + -fdiagnostics-color=always +) + +# +# From Bluez >= v5.64 the mgmt_ltk_info struct is changed due to inclusive language changes. +# https://github.com/bluez/bluez/commit/b7d6a7d25628e9b521a29a5c133fcadcedeb2102 +# +include(CheckStructHasMember) +check_struct_has_member("struct mgmt_ltk_info" central "../include/bluetooth/bluetooth.h;../include/bluetooth/mgmt.h" NO_INCLUSIVE_LANGUAGE LANGUAGE C) + +if(${NO_INCLUSIVE_LANGUAGE}) + message(VERBOSE "Your version of bluez don't uses inclusive language anymore") + target_compile_definitions(${TARGET} PUBLIC NO_INCLUSIVE_LANGUAGE) +endif() + +install( + TARGETS ${TARGET} EXPORT ${TARGET}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetooth/audio COMPONENT ${NAMESPACE}_Development + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE} +) + +install( + FILES ${CODEC_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetooth/audio/codecs +) + +InstallPackageConfig( + TARGETS ${TARGET} + DESCRIPTION "${PROJECT_DESCRIPTION}" +) + +InstallCMakeConfig( + TARGETS ${TARGET} +) diff --git a/Source/extensions/bluetooth/audio/DataRecord.h b/Source/extensions/bluetooth/audio/DataRecord.h new file mode 100644 index 000000000..f1b743433 --- /dev/null +++ b/Source/extensions/bluetooth/audio/DataRecord.h @@ -0,0 +1,502 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include +#include + + +namespace Thunder { + +namespace Bluetooth { + + class Buffer : public std::basic_string { + public: + using basic_string::basic_string; + + Buffer(const uint16_t size) + : basic_string() + { + resize(size); + } + ~Buffer() = default; + + public: + const string ToString() const + { + string val; + Core::ToHexString(data(), size(), val); + return (val.empty() == true? string(_T("")) : val); + } + }; + + // Byteorder neutral version + class EXTERNAL DataRecord { + public: + DataRecord(const DataRecord& buffer) = delete; + DataRecord& operator=(const DataRecord&) = delete; + + DataRecord() + : _buffer(nullptr) + , _bufferSize(0) + , _filledSize(0) + , _readerOffset(0) + , _writerOffset(0) + { + } + DataRecord(const uint8_t buffer[], const uint16_t bufferSize) + // It is ensured that the buffer will never be written. + : _buffer(const_cast(buffer)) + , _bufferSize(bufferSize) + , _filledSize(bufferSize) + , _readerOffset(0) + , _writerOffset(bufferSize) + { + ASSERT(_buffer != nullptr); + ASSERT(_bufferSize != 0); + ASSERT(_bufferSize >= _filledSize); + } + DataRecord(uint8_t scratchPad[], const uint16_t scratchPadSize, const uint16_t filledSize) + : _buffer(scratchPad) + , _bufferSize(scratchPadSize) + , _filledSize(filledSize) + , _readerOffset(0) + , _writerOffset(filledSize) + { + ASSERT(_buffer != nullptr); + ASSERT(_bufferSize != 0); + ASSERT(_bufferSize >= _filledSize); + } + DataRecord(const Buffer& buffer) + // It is ensured that the buffer will never be written. + : _buffer(const_cast(buffer.data())) + , _bufferSize(buffer.size()) + , _filledSize(buffer.size()) + , _readerOffset(0) + , _writerOffset(buffer.size()) + { + ASSERT(buffer.size() < 0x10000); + ASSERT(_buffer != nullptr); + ASSERT(_bufferSize >= _filledSize); + } + DataRecord(Buffer& scratchPad, const uint16_t filledSize) + : _buffer(const_cast(scratchPad.data())) + , _bufferSize(scratchPad.size()) + , _filledSize(filledSize) + , _readerOffset(0) + , _writerOffset(filledSize) + { + ASSERT(_buffer != nullptr); + ASSERT(_bufferSize != 0); + ASSERT(_bufferSize >= _filledSize); + } + ~DataRecord() = default; + + public: + bool IsEmpty() const { + return (Available() == 0); + } + uint16_t Length() const { + return (_writerOffset); + } + uint16_t Capacity() const { + return (_bufferSize); + } + uint16_t Free() const { + return ((_bufferSize > _writerOffset) && (_buffer != nullptr)? (_bufferSize - _writerOffset) : 0); + } + uint16_t Available() const { + return ((_writerOffset > _readerOffset) && (_buffer != nullptr)? (_writerOffset - _readerOffset) : 0); + } + uint16_t Position() const { + return (_readerOffset); + } + const uint8_t* Data() const { + return (_buffer); + } + + public: + void Assign(uint8_t buffer[], const uint16_t bufferSize) + { + _buffer = buffer; + _bufferSize = bufferSize; + _filledSize = bufferSize; + _writerOffset = bufferSize; + _readerOffset = 0; + } + void Assign(uint8_t scratchPad[], const uint16_t scratchPadSize, const uint16_t filledSize) + { + _buffer = scratchPad; + _bufferSize = scratchPadSize; + _filledSize = filledSize; + _writerOffset = filledSize; + _readerOffset = 0; + } + void Clear() + { + _writerOffset = 0; + _filledSize = 0; + _readerOffset = 0; + } + void Seek(const uint16_t offset = 0) + { + ASSERT(offset < _bufferSize); + _writerOffset = offset; + } + void Advance(const uint16_t length) + { + ASSERT(_writerOffset + length < _bufferSize); + _writerOffset += length; + } + void Rewind(const uint16_t offset = 0) const + { + ASSERT(offset < _bufferSize); + _readerOffset = offset; + } + void Skip(const uint16_t length) const + { + ASSERT(_readerOffset + length < _bufferSize); + _readerOffset += length; + } + const string ToString() const + { + string val; + Core::ToHexString(Data(), Length(), val); + return (val.empty() == true? string(_T("")) : val); + } + void Export(Buffer& output) const + { + output.assign(Data(), Length()); + } + operator Buffer() const + { + if (IsEmpty() == true) { + return (Buffer()); + } else { + return (Buffer(Data(), Length())); + } + } + + public: + void Fill(const uint16_t size, uint8_t value = 0) + { + ::memset(WritePtr(), value, size); + _writerOffset += size; + } + void Push(const bool value) + { + ASSERT(Free() > sizeof(uint8_t)); + Push(static_cast(value)); + } + void Push(const string& value) + { + ASSERT(value.size() < 0x10000); + ASSERT(Free() >= value.size()); + Push(reinterpret_cast(value.data()), static_cast(value.length())); + } + void Push(const Buffer& value) + { + ASSERT(value.size() < 0x10000); + ASSERT(Free() >= value.size()); + Push(value.data(), static_cast(value.length())); + } + void Push(const uint8_t value[], const uint16_t size) + { + ASSERT(Free() >= size); + ::memcpy(WritePtr(), value, size); + _writerOffset += size; + } + void Push(const DataRecord& element) + { + ASSERT(Free() >= element.Length()); + ::memcpy(WritePtr(), element.Data(), element.Length()); + _writerOffset += element.Length(); + } + + public: + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) == 1, "Only 8-bit integers push supported in DataRecord, choose endian-aware version"); + PushIntegerValue(static_cast::type>(value)); + } + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) == 1, "Only 8-bit integers push supported in DataRecord, choose endian-aware version"); + Push(static_cast::type>(value)); + } + + public: + void Pop(string& value, const uint16_t length) const + { + if (Available() >= length) { + value.assign(reinterpret_cast(ReadPtr()), length); + _readerOffset += length; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + value.clear(); + } + } + void Pop(Buffer& value, const uint16_t length) const + { + if (Available() >= length) { + value.assign(ReadPtr(), length); + _readerOffset += length; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + value.clear(); + } + } + void Pop(DataRecord& element, const uint16_t size) const + { + if (Available() >= size) { + element.Push(ReadPtr(), size); + _readerOffset += size; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + element.Clear(); + } + } + void PopAssign(DataRecord& element, const uint16_t size) const + { + if (Available() >= size) { + element.Assign(const_cast(ReadPtr()), size); + _readerOffset += size; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + element.Clear(); + } + } + + public: + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) == 1, "Only 8-bit integers pop supported in DataRecord, choose endian-aware version"); + typename std::underlying_type::type temp{}; + PopIntegerValue(temp); + value = static_cast(temp); + } + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) == 1, "Only 8-bit integers pop supported in DataRecord, choose endian-aware version"); + PopIntegerValue(value); + } + + protected: + const uint8_t* ReadPtr() const { + return (&_buffer[_readerOffset]); + } + uint8_t* WritePtr() { + return (&_buffer[_writerOffset]); + } + + protected: + void PushIntegerValue(const uint8_t value) + { + ASSERT(Free() >= 1); + _buffer[_writerOffset++] = value; + } + void PopIntegerValue(uint8_t& value) const + { + if (Available() >= 1) { + value = _buffer[_readerOffset++]; + } else { + TRACE_L1("DataRecord: Truncated payload"); + value = 0; + } + } + + protected: + uint8_t* _buffer; + uint16_t _bufferSize; + uint16_t _filledSize; + mutable uint16_t _readerOffset; + uint16_t _writerOffset; + }; // class DataRecord + + // Big-Endian version + class EXTERNAL DataRecordBE : public DataRecord { + public: + using DataRecord::DataRecord; + ~DataRecordBE() = default; + + using DataRecord::Pop; + using DataRecord::Push; + using DataRecord::PopIntegerValue; + using DataRecord::PushIntegerValue; + + public: + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) <= 4 , "Up to 32-bit integers supported in DataRecordBE"); + PushIntegerValue(static_cast::type>(value)); + } + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) <= 4, "Up to 32-bit enum push supported in DataRecordBE"); + Push(static_cast::type>(value)); + } + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) <= 4 , "Up to 32-bit integers supported in DataRecordBE"); + PopIntegerValue(value); + } + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) <= 4, "Up to 32-bit enum pop supported in DataRecordBE"); + typename std::underlying_type::type temp{}; + PopIntegerValue(temp); + value = static_cast(temp); + } + + protected: + void PushIntegerValue(const uint16_t value) + { + ASSERT(Free() >= 2); + _buffer[_writerOffset++] = (value >> 8); + _buffer[_writerOffset++] = value; + } + void PushIntegerValue(const uint32_t value) + { + ASSERT(Free() >= 4); + _buffer[_writerOffset++] = (value >> 24); + _buffer[_writerOffset++] = (value >> 16); + _buffer[_writerOffset++] = (value >> 8); + _buffer[_writerOffset++] = value; + } + void PopIntegerValue(uint16_t& value) const + { + if (Available() >= 2) { + value = ((_buffer[_readerOffset] << 8) | _buffer[_readerOffset + 1]); + _readerOffset += 2; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + value = 0; + } + } + void PopIntegerValue(uint32_t& value) const + { + if (Available() >= 4) { + value = ((_buffer[_readerOffset] << 24) | (_buffer[_readerOffset + 1] << 16) + | (_buffer[_readerOffset + 2] << 8) | _buffer[_readerOffset + 3]); + _readerOffset += 4; + } else { + TRACE_L1("DataRecord: Truncated payload"); + _readerOffset = _writerOffset; + value = 0; + } + } + }; // class DataRecordLE + + // Little-Endian version + class EXTERNAL DataRecordLE : public DataRecord { + public: + using DataRecord::DataRecord; + ~DataRecordLE() = default; + + using DataRecord::Pop; + using DataRecord::Push; + using DataRecord::PopIntegerValue; + using DataRecord::PushIntegerValue; + + public: + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) <= 4 , "Up to 32-bit integers supported in DataRecordLE"); + PushIntegerValue(static_cast::type>(value)); + } + template::value, int>::type = 0> + void Push(const TYPE value) + { + static_assert(sizeof(TYPE) <= 4, "Up to 32-bit enum push supported in DataRecordLE"); + Push(static_cast::type>(value)); + } + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) <= 4 , "Up to 32-bit integers supported in DataRecordLE"); + PopIntegerValue(value); + } + template::value, int>::type = 0> + void Pop(TYPE& value) const + { + static_assert(sizeof(TYPE) <= 4, "Up to 32-bit enum pop supported in DataRecordLE"); + typename std::underlying_type::type temp{}; + PopIntegerValue(temp); + value = static_cast(temp); + } + + protected: + void PushIntegerValue(const uint16_t value) + { + ASSERT(Free() >= 2); + _buffer[_writerOffset++] = value; + _buffer[_writerOffset++] = (value >> 8); + } + void PushIntegerValue(const uint32_t value) + { + ASSERT(Free() >= 4); + _buffer[_writerOffset++] = value; + _buffer[_writerOffset++] = (value >> 8); + _buffer[_writerOffset++] = (value >> 16); + _buffer[_writerOffset++] = (value >> 24); + } + void PopIntegerValue(uint16_t& value) const + { + if (Available() >= 2) { + value = ((_buffer[_readerOffset + 1] << 8) | _buffer[_readerOffset]); + _readerOffset += 2; + } else { + TRACE_L1("DataRecordLE: Truncated payload"); + _readerOffset = _writerOffset; + value = 0; + } + } + void PopIntegerValue(uint32_t& value) const + { + if (Available() >= 4) { + value = ((_buffer[_readerOffset + 3] << 24) | (_buffer[_readerOffset + 2] << 16) + | (_buffer[_readerOffset + 1] << 8) | _buffer[_readerOffset]); + _readerOffset += 4; + } else { + TRACE_L1("DataRecordLE: Truncated payload"); + _readerOffset = _writerOffset; + value = 0; + } + } + }; // class DataRecordLE + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/IAudioCodec.h b/Source/extensions/bluetooth/audio/IAudioCodec.h new file mode 100644 index 000000000..804dcdca4 --- /dev/null +++ b/Source/extensions/bluetooth/audio/IAudioCodec.h @@ -0,0 +1,74 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace A2DP { + + struct IAudioCodec { + + static constexpr uint8_t MEDIA_TYPE = 0x00; // audio + + enum codectype : uint8_t { + LC_SBC = 0 + }; + + struct StreamFormat { + uint32_t SampleRate; + uint16_t FrameRate; + uint8_t Resolution; + uint8_t Channels; + }; + + virtual ~IAudioCodec() = default; + + virtual codectype Type() const = 0; + + virtual uint32_t BitRate() const = 0; // bits per second + + virtual uint16_t RawFrameSize() const = 0; + virtual uint16_t EncodedFrameSize() const = 0; + + virtual uint32_t QOS(const int8_t policy) = 0; + + virtual uint32_t Configure(const StreamFormat& format, const string& settings) = 0; + virtual uint32_t Configure(const uint8_t stream[], const uint16_t length) = 0; + + virtual void Configuration(StreamFormat& format, string& settings) const = 0; + + virtual uint16_t Encode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outBufferSize, uint8_t outBuffer[]) const = 0; + + virtual uint16_t Decode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outBufferSize, uint8_t outBuffer[]) const = 0; + + virtual uint16_t Serialize(const bool capabilities, uint8_t stream[], const uint16_t length) const = 0; + }; + +} // namespace A2DP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/IAudioContentProtection.h b/Source/extensions/bluetooth/audio/IAudioContentProtection.h new file mode 100644 index 000000000..43456c8f6 --- /dev/null +++ b/Source/extensions/bluetooth/audio/IAudioContentProtection.h @@ -0,0 +1,56 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace A2DP { + + struct IAudioContentProtection { + + enum protectiontype { + SCMS_T + }; + + virtual ~IAudioContentProtection() = default; + + virtual protectiontype Type() const = 0; + + virtual uint32_t Configure(const string& format) = 0; + virtual void Configuration(string& format) const = 0; + + virtual uint32_t Protect(const uint16_t inBufferSize, const uint8_t inBuffer[], + const uint16_t maxOutBufferSize, uint8_t outBuffer) const = 0; + + virtual uint32_t Unprotect(const uint16_t inBufferSize, const uint8_t inBuffer[], + const uint16_t maxOutBufferSize, uint8_t outBuffer) const = 0; + + virtual uint32_t Serialize(const bool capabilities, uint8_t buffer[], const uint16_t length ) const = 0; + }; + +} // namespace A2DP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/Module.cpp b/Source/extensions/bluetooth/audio/Module.cpp new file mode 100644 index 000000000..b734eff3c --- /dev/null +++ b/Source/extensions/bluetooth/audio/Module.cpp @@ -0,0 +1,22 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Source/extensions/bluetooth/audio/Module.h b/Source/extensions/bluetooth/audio/Module.h new file mode 100644 index 000000000..9cb529d5a --- /dev/null +++ b/Source/extensions/bluetooth/audio/Module.h @@ -0,0 +1,39 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME Bluetooth_Audio +#endif + +#include +#include + +#include <../include/bluetooth/bluetooth.h> +#include <../include/bluetooth/l2cap.h> + +#include "../Debug.h" +#include "../UUID.h" + +#if defined(__WINDOWS__) && defined(BLUETOOTH_EXPORTS) +#undef EXTERNAL +#define EXTERNAL EXTERNAL_EXPORT +#endif + diff --git a/Source/extensions/bluetooth/audio/RTPSocket.h b/Source/extensions/bluetooth/audio/RTPSocket.h new file mode 100644 index 000000000..c57b3841b --- /dev/null +++ b/Source/extensions/bluetooth/audio/RTPSocket.h @@ -0,0 +1,308 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "IAudioCodec.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace RTP { + + class EXTERNAL MediaPacket { + public: + MediaPacket(const uint8_t payloadType, const uint32_t synchronisationSource, + const uint16_t sequence, const uint32_t timestamp) + : _packet(nullptr) + , _packetLength(0) + , _sequence(sequence) + , _timestamp(timestamp) + , _synchronisationSource(synchronisationSource) + , _payload(nullptr) + , _payloadLength(0) + , _payloadType(payloadType) + { + } + MediaPacket(const uint8_t packet[], const uint16_t packetLength) + : _packet(packet) + , _packetLength(packetLength) + , _sequence(0) + , _timestamp(0) + , _synchronisationSource(0) + , _payload(nullptr) + , _payloadLength(0) + , _payloadType(0) + { + ASSERT(packet != nullptr); + + if (packetLength >= 12) { + DataRecordBE pkt(packet, packetLength); + + uint8_t octet; + pkt.Pop(octet); + + uint8_t csrcCount = (octet & 0x1F); + const bool extension = (octet & 16); + const bool padding = (octet & 32); + const uint8_t version = (octet >> 6); + + if (version == 2) { + pkt.Pop(octet); + _payloadType = (octet & 0x7F); + _marker = !!(octet & 0x80); + + pkt.Pop(_sequence); + pkt.Pop(_timestamp); + pkt.Pop(_synchronisationSource); // ssrc + + // Ignore contributing source IDs... + while (csrcCount--) { + pkt.Skip(sizeof(uint32_t)); // csrc + } + + // Ignore the application-specific extension. + if (extension == true) { + TRACE_L1("Have extension in RTP packet!"); + pkt.Skip(sizeof(uint32_t)); // extension id + + uint32_t extensionLength; + pkt.Pop(extensionLength); + pkt.Skip(extensionLength); + } + + // Any padding to remove? + const uint8_t paddingLength = (padding? _packet[_packetLength - 1] : 0); + + // Isolate the payload data + _payload = (_packet + pkt.Position()); + _payloadLength = (_packetLength - pkt.Position() - paddingLength); + } + else { + TRACE_L1("Unsupported RTP packet version!"); + } + } + else { + TRACE_L1("Invalid RTP packet!"); + } + } + + public: + uint16_t Pack(const A2DP::IAudioCodec& codec, const uint8_t payload[], const uint16_t payloadLength, + uint8_t buffer[], const uint16_t bufferLength) + { + ASSERT(bufferLength >= 12); + ASSERT(buffer != nullptr); + + DataRecordBE pkt(buffer, bufferLength, 0); + + pkt.Push(static_cast(2 << 6)); + pkt.Push(_payloadType); + pkt.Push(_sequence); + pkt.Push(_timestamp); + pkt.Push(_synchronisationSource); + + const uint16_t headerLength = pkt.Length(); + ASSERT(headerLength == 12); + + uint16_t length = (bufferLength - headerLength); + const uint16_t consumed = codec.Encode(payloadLength, payload, length, (buffer + headerLength)); + + _packet = buffer; + _payload = (_packet + headerLength); + _packetLength = (headerLength + length); + _payloadLength = length; + + return (consumed); + } + uint16_t Unpack(const A2DP::IAudioCodec& codec, uint8_t buffer[], uint16_t& length) + { + ASSERT(buffer != nullptr); + ASSERT(Payload() != nullptr); + ASSERT(length != 0); + + return (codec.Decode(PayloadLength(), Payload(), length, buffer)); + } + + public: + bool IsValid() const { + return ((_payloadType != 0) && (_payloadLength != 0)); + } + uint16_t Sequence() const { + return (_sequence); + } + uint32_t Timestamp() const { + return (_timestamp); + } + uint8_t Type() const { + return (_payloadType); + } + uint32_t SyncSource() const { + return (_synchronisationSource); + } + const uint8_t* Data() const { + return (_packet); + } + const uint16_t Length() const { + return (_packetLength); + } + const uint8_t* Payload() const { + return (_payload); + } + const uint16_t PayloadLength() const { + return (_payloadLength); + } + + private: + const uint8_t* _packet; + uint16_t _packetLength; + uint16_t _sequence; + uint32_t _timestamp; + uint32_t _synchronisationSource; + const uint8_t* _payload; + uint16_t _payloadLength; + uint8_t _payloadType; + bool _marker; + }; + + template + class EXTERNAL OutboundMediaPacketType : public MediaPacket, public Core::IOutbound { + public: + static_assert(SIZE >= 48, "Too small packet buffer"); + + OutboundMediaPacketType(const OutboundMediaPacketType&) = delete; + OutboundMediaPacketType& operator=(const OutboundMediaPacketType&) = delete; + ~OutboundMediaPacketType() = default; + + OutboundMediaPacketType(const uint16_t MTU, const uint32_t synchronisationSource, uint16_t sequence, const uint32_t timestamp) + : MediaPacket(TYPE, synchronisationSource, sequence, timestamp) + , _buffer() + , _offset(0) + , _mtu(MTU) + { + ASSERT(MTU <= sizeof(_buffer)); + } + + public: + uint16_t Ingest(const A2DP::IAudioCodec& codec, const uint8_t data[], const uint16_t dataLength) + { + ASSERT(data != nullptr); + + return (Pack(codec, data, dataLength, _buffer, _mtu)); + } + + private: + void Reload() const override + { + _offset = 0; + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + ASSERT(stream != nullptr); + + uint16_t result = std::min((Length() - _offset), length); + if (result > 0) { + ::memcpy(stream, (Data() + _offset), result); + _offset += result; + + // CMD_DUMP("RTP send", stream, result); + } + + return (result); + } + + private: + uint8_t _buffer[SIZE]; + mutable uint16_t _offset; + uint16_t _mtu; + }; // class OutboundMediaPacketType + + class EXTERNAL ClientSocket : public Core::SynchronousChannelType { + public: + static constexpr uint32_t CommunicationTimeout = 500; + + public: + ClientSocket(const ClientSocket&) = delete; + ClientSocket& operator=(const ClientSocket&) = delete; + ~ClientSocket() = default; + + ClientSocket(const Core::NodeId& localNode, const Core::NodeId& remoteNode) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, localNode, remoteNode, 2048, 2048) + , _outputMTU(0) + { + } + + uint16_t OutputMTU() const { + return (_outputMTU); + } + + public: + virtual void Operational(const bool upAndRunning) = 0; + + private: + void StateChange() override + { + Core::SynchronousChannelType::StateChange(); + + if (IsOpen() == true) { + struct l2cap_options options{}; + socklen_t len = sizeof(options); + + ::getsockopt(Handle(), SOL_L2CAP, L2CAP_OPTIONS, &options, &len); + + ASSERT(options.omtu <= SendBufferSize()); + ASSERT(options.imtu <= ReceiveBufferSize()); + + _outputMTU = options.omtu; + + TRACE(Trace::Information, (_T("Transport channel input MTU: %d, output MTU: %d"), options.imtu, options.omtu)); + + Operational(true); + } else { + Operational(false); + } + } + + private: + uint16_t Deserialize(const uint8_t dataFrame[], const uint16_t availableData) override + { + // Do not expect anything inbound on this channel... + + uint32_t result = 0; + + if (availableData != 0) { + TRACE_L1("Unexpected RTP data received [%d bytes]", availableData); + + CMD_DUMP("RTP received unexpected", dataFrame, availableData); + } + + return (result); + } + + private: + uint16_t _outputMTU; + }; // class ClientSocket + +} // namespace RTP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/SDPProfile.cpp b/Source/extensions/bluetooth/audio/SDPProfile.cpp new file mode 100644 index 000000000..99a43682e --- /dev/null +++ b/Source/extensions/bluetooth/audio/SDPProfile.cpp @@ -0,0 +1,927 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +#include "SDPSocket.h" +#include "SDPProfile.h" + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(Bluetooth::SDP::ClassID::id) + { Bluetooth::SDP::ClassID::Undefined, _TXT("(undefined)") }, + + // Protocols + { Bluetooth::SDP::ClassID::SDP, _TXT("SDP") }, + { Bluetooth::SDP::ClassID::UDP, _TXT("UDP") }, + { Bluetooth::SDP::ClassID::RFCOMM, _TXT("RFCOMM") }, + { Bluetooth::SDP::ClassID::TCP, _TXT("TCP") }, + { Bluetooth::SDP::ClassID::TCS_BIN, _TXT("TCS-Binary") }, + { Bluetooth::SDP::ClassID::TCS_AT, _TXT("TCS-AT") }, + { Bluetooth::SDP::ClassID::ATT, _TXT("ATT") }, + { Bluetooth::SDP::ClassID::OBEX, _TXT("OBEX") }, + { Bluetooth::SDP::ClassID::IP, _TXT("IP") }, + { Bluetooth::SDP::ClassID::FTP, _TXT("FTP") }, + { Bluetooth::SDP::ClassID::HTTP, _TXT("HTTP") }, + { Bluetooth::SDP::ClassID::WSP, _TXT("WSP") }, + { Bluetooth::SDP::ClassID::BNEP, _TXT("BNEP") }, + { Bluetooth::SDP::ClassID::UPNP, _TXT("UPNP/ESDP") }, + { Bluetooth::SDP::ClassID::HIDP, _TXT("HIDP") }, + { Bluetooth::SDP::ClassID::HCRP_CTRL, _TXT("HCRP-ControlChannel") }, + { Bluetooth::SDP::ClassID::HCRP_DATA, _TXT("HCRP-DataChannel") }, + { Bluetooth::SDP::ClassID::HCRP_NOTE, _TXT("HCRP-Notification") }, + { Bluetooth::SDP::ClassID::AVCTP, _TXT("AVCTP") }, + { Bluetooth::SDP::ClassID::AVDTP, _TXT("AVDTP") }, + { Bluetooth::SDP::ClassID::CMTP, _TXT("CMTP") }, + { Bluetooth::SDP::ClassID::UDI, _TXT("UDI") }, + { Bluetooth::SDP::ClassID::MCAP_CTRL, _TXT("MCAP-ControlChannel") }, + { Bluetooth::SDP::ClassID::MCAP_DATA, _TXT("MCAP-DataChannel") }, + { Bluetooth::SDP::ClassID::L2CAP, _TXT("L2CAP") }, + + // SDP services + { Bluetooth::SDP::ClassID::ServiceDiscoveryServer, _TXT("Service Discovery Server") }, + { Bluetooth::SDP::ClassID::BrowseGroupDescriptor, _TXT("Browse Group Descriptor") }, + { Bluetooth::SDP::ClassID::PublicBrowseRoot, _TXT("Public Browse Root") }, + + // Services and Profiles + { Bluetooth::SDP::ClassID::SerialPort, _TXT("Serial Port (SPP)") }, + { Bluetooth::SDP::ClassID::LANAccessUsingPPP, _TXT("LAN Access Using PPP (LAP)") }, + { Bluetooth::SDP::ClassID::DialupNetworking, _TXT("Dial-Up Networking (DUN)") }, + { Bluetooth::SDP::ClassID::IrMCSync, _TXT("IrMC Sync (SYNCH)") }, + { Bluetooth::SDP::ClassID::OBEXObjectPush, _TXT("OBEX Obect Push (OPP)") }, + { Bluetooth::SDP::ClassID::OBEXFileTransfer, _TXT("OBEX File Transfer (FTP)") }, + { Bluetooth::SDP::ClassID::IrMCSyncCommand, _TXT("IrMC Sync Command (SYNCH)") }, + { Bluetooth::SDP::ClassID::HeadsetHSP, _TXT("Headset (HSP)") }, + { Bluetooth::SDP::ClassID::CordlessTelephony, _TXT("Cordless Telephony (CTP)") }, + { Bluetooth::SDP::ClassID::AudioSource, _TXT("Audio Source (A2DP)") }, + { Bluetooth::SDP::ClassID::AudioSink, _TXT("Audio Sink (A2DP)") }, + { Bluetooth::SDP::ClassID::AVRemoteControlTarget, _TXT("A/V Remote Control Target (AVRCP)") }, + { Bluetooth::SDP::ClassID::AdvancedAudioDistribution, _TXT("Advanced Audio Distribution (A2DP)") }, + { Bluetooth::SDP::ClassID::AVRemoteControl, _TXT("A/V Remote Control (AVRCP)") }, + { Bluetooth::SDP::ClassID::AVRemoteControlController, _TXT("A/V Remote Control Controller (AVRCP)") }, + { Bluetooth::SDP::ClassID::Intercom, _TXT("Intercom (ICP)") }, + { Bluetooth::SDP::ClassID::Fax, _TXT("Fax (FAX)") }, + { Bluetooth::SDP::ClassID::HeadsetAudioGateway, _TXT("Headset Audio Gateway (HSP)") }, + { Bluetooth::SDP::ClassID::WAP, _TXT("WAP (WAPB)") }, + { Bluetooth::SDP::ClassID::WAPClient, _TXT("WAP Client (WAPB)") }, + { Bluetooth::SDP::ClassID::PANU, _TXT("PAN User (PAN)") }, + { Bluetooth::SDP::ClassID::NAP, _TXT("Network Access Point (PAN)") }, + { Bluetooth::SDP::ClassID::GN, _TXT("Group Ad-hoc Network (PAN)") }, + { Bluetooth::SDP::ClassID::DirectPrinting, _TXT("Direct Printing (BPP)") }, + { Bluetooth::SDP::ClassID::ReferencePrinting, _TXT("Reference Printing (BPP)") }, + { Bluetooth::SDP::ClassID::BasicImagingProfile, _TXT("Basic Imaging (BIP)") }, + { Bluetooth::SDP::ClassID::ImagingResponder, _TXT("Imaging Responder (BIP)") }, + { Bluetooth::SDP::ClassID::ImagingAutomaticArchive, _TXT("Imaging Automatic Archive (BIP)") }, + { Bluetooth::SDP::ClassID::ImagingReferencedObjects, _TXT("Imaging Referenced Objects (BIP)") }, + { Bluetooth::SDP::ClassID::Handsfree, _TXT("Hands-Free (HFP)") }, + { Bluetooth::SDP::ClassID::HandsfreeAudioGateway, _TXT("Hands-Free Audio Gateway (HFP)") }, + { Bluetooth::SDP::ClassID::DirectPrintingReferenceObjects, _TXT("Direct Printing Reference Objects (BPP)") }, + { Bluetooth::SDP::ClassID::ReflectedUI, _TXT("Reflected UI (BPP)") }, + { Bluetooth::SDP::ClassID::BasicPrinting, _TXT("Basic Printing (BPP)") }, + { Bluetooth::SDP::ClassID::PrintingStatus, _TXT("Printing Status (BPP)") }, + { Bluetooth::SDP::ClassID::HumanInterfaceDeviceService, _TXT("Human Interface Device (HID)") }, + { Bluetooth::SDP::ClassID::HardcopyCableReplacement, _TXT("Hardcopy Cable Replacement (HCRP)") }, + { Bluetooth::SDP::ClassID::HCRPrint, _TXT("HCRP Print (HCRP) ") }, + { Bluetooth::SDP::ClassID::HCRScan, _TXT("HCRP Scan (HCRP)") }, + { Bluetooth::SDP::ClassID::CommonISDNAccess, _TXT("Common ISDN Access (CIP)") }, + { Bluetooth::SDP::ClassID::SIMAccess, _TXT("SIM Access (SAP)") }, + { Bluetooth::SDP::ClassID::PhonebookAccessPCE, _TXT("Phonebook Client Equipment (PBAP)") }, + { Bluetooth::SDP::ClassID::PhonebookAccessPSE, _TXT("Phonebook Server Equipment (PBAP)") }, + { Bluetooth::SDP::ClassID::PhonebookAccess, _TXT("Phonebook Access (PBAP)") }, + { Bluetooth::SDP::ClassID::HeadsetHS, _TXT("Headset v1.2 (HSP)") }, + { Bluetooth::SDP::ClassID::MessageAccessServer, _TXT("Message Access Server (MAP)") }, + { Bluetooth::SDP::ClassID::MessageNotificationServer, _TXT("Message Notification Server (MAP)") }, + { Bluetooth::SDP::ClassID::MessageAccess, _TXT("Message Access(MAP)") }, + { Bluetooth::SDP::ClassID::GNSS, _TXT("Global Navigation Satellite System (GNSS)") }, + { Bluetooth::SDP::ClassID::GNSSServer, _TXT("GNSS Server (GNSS)") }, + { Bluetooth::SDP::ClassID::ThreeDDisplay, _TXT("3D Display (3DSP)") }, + { Bluetooth::SDP::ClassID::ThreeDGlasses, _TXT("3D Glasses (3DSP)") }, + { Bluetooth::SDP::ClassID::ThreeDSynchronisation, _TXT("3D Synchronisation (3DSP)") }, + { Bluetooth::SDP::ClassID::MPS, _TXT("Multi-Profile Specification (MPS)") }, + { Bluetooth::SDP::ClassID::MPSSC, _TXT("MPS SC (MPS)") }, + { Bluetooth::SDP::ClassID::CTNAccessService, _TXT("CTN Access (CTN)") }, + { Bluetooth::SDP::ClassID::CTNNotificationService, _TXT("CTN Notification (CTN)") }, + { Bluetooth::SDP::ClassID::CTN, _TXT("Calendar Tasks and Notes (CTN)") }, + { Bluetooth::SDP::ClassID::PnPInformation, _TXT("PnP Information (DID") }, + { Bluetooth::SDP::ClassID::GenericNetworking, _TXT("Generic Networking") }, + { Bluetooth::SDP::ClassID::GenericFileTransfer, _TXT("Generic File Transfer") }, + { Bluetooth::SDP::ClassID::GenericAudio, _TXT("Generic Audio") }, + { Bluetooth::SDP::ClassID::GenericTelephony, _TXT("Generic Telephony") }, + { Bluetooth::SDP::ClassID::UPNPService, _TXT("UPNP (ESDP)") }, + { Bluetooth::SDP::ClassID::UPNPIPService, _TXT("UPNP IP (ESDP)") }, + { Bluetooth::SDP::ClassID::ESDPUPNPIPPAN, _TXT("UPNP IP PAN (ESDP)") }, + { Bluetooth::SDP::ClassID::ESDPUPNPIPLAP, _TXT("UPNP IP LAP (ESDP)") }, + { Bluetooth::SDP::ClassID::ESDPUPNPL2CAP, _TXT("UPNP L2CAP (ESDP)") }, + { Bluetooth::SDP::ClassID::VideoSource, _TXT("Video Source (VDP)") }, + { Bluetooth::SDP::ClassID::VideoSink, _TXT("Video Sink (VDP)") }, + { Bluetooth::SDP::ClassID::VideoDistribution, _TXT("Video Distribution Profile (VDP)") }, + { Bluetooth::SDP::ClassID::HDP, _TXT("Health Device (HDP)") }, + { Bluetooth::SDP::ClassID::HDPSource, _TXT("HDP Source (HDP)") }, + { Bluetooth::SDP::ClassID::HDPSink, _TXT("HDP Sink (HDP)") }, +ENUM_CONVERSION_END(Bluetooth::SDP::ClassID::id) + +ENUM_CONVERSION_BEGIN(Bluetooth::SDP::Service::AttributeDescriptor::id) + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceRecordHandle, _TXT("ServiceRecordHandle") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceClassIDList, _TXT("ServiceClassIDList") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceRecordState, _TXT("ServiceRecordState") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceID, _TXT("ServiceID") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ProtocolDescriptorList, _TXT("ProtocolDescriptorList") }, + { Bluetooth::SDP::Service::AttributeDescriptor::BrowseGroupList, _TXT("BrowseGroupList") }, + { Bluetooth::SDP::Service::AttributeDescriptor::LanguageBaseAttributeIDList, _TXT("LanguageBaseAttributeIDList") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceInfoTimeToLive, _TXT("ServiceInfoTimeToLive") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ServiceAvailability, _TXT("ServiceAvailability") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ProfileDescriptorList, _TXT("BluetoothProfileDescriptorList") }, + { Bluetooth::SDP::Service::AttributeDescriptor::DocumentationURL, _TXT("DocumentationURL") }, + { Bluetooth::SDP::Service::AttributeDescriptor::ClientExecutableURL, _TXT("ClientExecutableURL") }, + { Bluetooth::SDP::Service::AttributeDescriptor::IconURL, _TXT("IconURL") }, +ENUM_CONVERSION_END(Bluetooth::SDP::Service::AttributeDescriptor::id) + +namespace Bluetooth { + +namespace SDP { + + // CLIENT --- + + uint32_t Client::Discover(const std::list& services, Tree& tree) const + { + // Convenience method to discover and add services and their attributes to a service tree. + + uint32_t result = Core::ERROR_NONE; + + std::vector handles; + + // First find all the requested services... + + if ((result = ServiceSearch(services, handles)) == Core::ERROR_NONE) { + + for (uint32_t& h : handles) { + std::list> attributes; + + // Then retrieve their attributes... + + if ((result = ServiceAttribute(h, std::list{ 0x0000FFFF } /* all of them */, attributes)) == Core::ERROR_NONE) { + + Service& service(tree.Add(h)); + + for (auto const& attr : attributes) { + service.Deserialize(attr.first, attr.second); + } + } else { + break; + } + } + } + + if (result != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Failed to retrieve Bluetooth services via SDP protocol"))); + } + + return (result); + } + + uint32_t Client::ServiceSearch(const std::list& services, std::vector& outHandles) const + { + ASSERT((services.size() > 0) && (services.size() <= 12)); // As per spec + + ClientSocket::Command command(_socket); + + const uint16_t maxResults = ((Capacity(command.Result()) - (2 * sizeof(uint32_t))) / sizeof(uint32_t)); + TRACE_L5("capacity=%d handles", maxResults); + + uint32_t result = Core::ERROR_NONE; + Buffer continuationData; + + outHandles.clear(); + + do { + // Handle fragmented packets by repeating the request until the continuation state is 0. + + command.Set(PDU::ServiceSearchRequest, [&](Payload& payload) { + + // ServiceSearchRequest frame format: + // - ServiceSearchPattern (sequence of UUIDs) + // - MaximumServiceRecordCount (word) + // - ContinuationState + + payload.Push(use_descriptor, services); + payload.Push(maxResults - outHandles.size()); + + ASSERT(continuationData.size() <= 16); + payload.Push(continuationData.size()); + + if (continuationData.empty() == false) { + payload.Push(continuationData); + } + }); + + result = Execute(command, [&](const Payload& payload) { + + // ServiceSearchResponse frame format: + // - TotalServiceRecordCount (long) + // - CurrentServiceRecordCount (long) + // - ServiceRecordHandleList (list of longs, not a sequence!) + // - ContinuationState + + if (payload.Available() < (2 * sizeof(uint16_t))) { + TRACE_L1("SDP: Truncated payload in ServiceSearchResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + else { + uint16_t totalCount{}; + payload.Pop(totalCount); + + uint16_t currentCount{}; + payload.Pop(currentCount); + + TRACE_L5("TotalServiceRecordCount=%d", totalCount); + TRACE_L5("CurrentServiceRecordCount=%d", currentCount); + + // Clip this in case of a phony value. + totalCount = std::min(32, totalCount); + currentCount = std::min(32, currentCount); + + outHandles.reserve(totalCount); + + if (payload.Available() >= (currentCount * sizeof(uint32_t))) { + + payload.Pop(outHandles, currentCount); + + for (uint16_t i=0; i < outHandles.size(); i++) { + TRACE_L5("ServiceRecordHandleList[%d]=0x%08x", i, outHandles[i]); + } + + if (payload.Available() >= sizeof(uint8_t)) { + uint8_t continuationDataLength{}; + payload.Pop(continuationDataLength); + + if ((continuationDataLength > 16) || (payload.Available() < continuationDataLength)) { + TRACE_L1("SDP: Invalid continuation data in ServiceSearchResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + else if (continuationDataLength > 0) { + payload.Pop(continuationData, continuationDataLength); + } + } + else { + TRACE_L1("SDP: Truncated payload in ServiceSearchResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + + if (payload.Available() != 0) { + TRACE_L1("SDP: Unexpected data in ServiceSearchResponse payload!"); + } + } else { + TRACE_L1("SDP: Truncated payload in ServiceSearchResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + } + }); + + } while ((continuationData.size() != 0) && (result == Core::ERROR_NONE)); + + TRACE_L1("SDP: ServiceSearch found %d matching service(s)", outHandles.size()); + + return (result); + } + + /* private */ + uint32_t Client::InternalServiceAttribute(const PDU::pduid id, + const std::function& buildCb, + const std::list& attributeIdRanges, + std::list>& outAttributes) const + { + ASSERT(buildCb != 0); + ASSERT((attributeIdRanges.size() > 0) && (attributeIdRanges.size() <= 256)); + + ClientSocket::Command command(_socket); + + const uint16_t maxByteCount = (Capacity(command.Result()) - sizeof(uint16_t)); + TRACE_L5("capacity=%d bytes", maxByteCount); + + // From client perspective ServiceAttribute and ServiceSearchAttribute are very similar, + // hence use one method to handle both. + + uint32_t result = Core::ERROR_NONE; + Buffer continuationData; + + do { + // Handle fragmented packets by repeating the request until the continuation state is 0. + + command.Set(id, [&](Payload& payload) { + + // Here's the difference between ServiceAttribute and ServiceSearchAttribute handled + // First sends a service handle, the second a list of UUIDs to scan for. + buildCb(payload); + + payload.Push(maxByteCount); + payload.Push(use_descriptor, attributeIdRanges); + + ASSERT(continuationData.size() <= 16); + payload.Push(continuationData.size()); + + if (continuationData.empty() == false) { + payload.Push(continuationData); + } + }); + + result = Execute(command, [&](const Payload& payload) { + + // ServiceAttributeResponse/ServiceSearchAttributeResponse frame format: + // - AttributeListByteCount (word) + // - AttributeList (sequence of long:data pairs, where data size is dependant on the attribute and may be a sequence) + // - ContinuationState + + if (payload.Available() < 2) { + TRACE_L1("SDP: Truncated Service(Search)AttributeResponse payload!"); + result = Core::ERROR_BAD_REQUEST; + } + else { + payload.Pop(use_length, [&](const Payload& buffer) { + buffer.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() >= 2) { + uint16_t attribute{}; + Buffer value; + + sequence.Pop(use_descriptor, attribute); + sequence.Pop(use_descriptor, value); + + TRACE_L5("attribute %d=[%s]", attribute, value.ToString().c_str()); + + outAttributes.emplace_back(attribute, std::move(value)); + } + + if (sequence.Available() != 0) { + TRACE_L1("SDP: Unexpected data in Service(Search)AttributeResponse!"); + } + }); + + if (buffer.Available() != 0) { + TRACE_L1("SDP: Unexpected data in Service(Search)AttributeResponse!"); + } + }); + + if (payload.Available() >= 1) { + uint8_t continuationDataLength{}; + payload.Pop(continuationDataLength); + + if ((continuationDataLength > 16) || (payload.Available() < continuationDataLength)) { + TRACE_L1("SDP: Invalid continuation data in Service(Search)AttributeResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + else { + payload.Pop(continuationData, continuationDataLength); + } + } + else { + TRACE_L1("SDP: Truncated payload in Service(Search)AttributeResponse!"); + result = Core::ERROR_BAD_REQUEST; + } + + if (payload.Available() != 0) { + TRACE_L1("SDP: Unexpected data in ServiceSearchAttributeResponse!"); + } + } + }); + } while ((continuationData.size() != 0) && (result == Core::ERROR_NONE)); + + TRACE_L1("SDP: Service(Search)Attribute found %d matching attribute(s)", outAttributes.size()); + + return (result); + } + + uint32_t Client::ServiceAttribute(const uint32_t serviceHandle, const std::list& attributeIdRanges, + std::list>& outAttributes) const + { + ASSERT(serviceHandle != 0); + + return (InternalServiceAttribute(PDU::ServiceAttributeRequest, [&](Payload& payload) { + + // ServiceAttributeRequest frame format: + // - ServiceRecordHandle (long) + // - MaximumAttributeByteCount (word) + // - AttributeIDList (sequence of UUID ranges or UUIDs) [only ranges supported in this client-side implementation] + // - ContinuationState + + payload.Push(serviceHandle); + + // MaximumAttributeByteCount, AttributeIDList and ContinuationState is handled by InternalServiceAttribute(). + + }, attributeIdRanges, outAttributes)); + } + + uint32_t Client::ServiceSearchAttribute(const std::list& services, const std::list& attributeIdRanges, + std::list>& outAttributes) const + { + ASSERT((services.size() > 0) && (services.size() <= 12)); + + return (InternalServiceAttribute(PDU::ServiceSearchAttributeRequest, [&](Payload& payload) { + + // ServiceSearchAttributeRequest frame format: + // - ServiceSearchPattern (sequence of UUIDs) + // - MaximumAttributeByteCount (word) + // - AttributeIDList (sequence of UUID ranges or UUIDs) [only ranges supported in this client-side implementation] + // - ContinuationState + + payload.Push(use_descriptor, services); + + // MaximumAttributeByteCount, AttributeIDList and ContinuationState is handled by InternalServiceAttribute(). + + }, attributeIdRanges, outAttributes)); + } + + /* private */ + uint32_t Client::Execute(ClientSocket::Command& command, const Payload::Inspector& inspectorCb) const + { + uint32_t result = Core::ERROR_ASYNC_FAILED; + + if (_socket.Exchange(ClientSocket::CommunicationTimeout, command, command) == Core::ERROR_NONE) { + + if (command.Result().Error() != PDU::Success) { + TRACE_L1("SDP server: Message %d failed! [%d]", command.Call().Type(), command.Result().Error()); + } + else if (command.Result().TransactionId() != command.Call().TransactionId()) { + TRACE_L1("SDP server: Mismatched message transaction ID!"); + } + else { + result = Core::ERROR_NONE; + + if (inspectorCb != nullptr) { + command.Result().InspectPayload(inspectorCb); + } + } + } + + return (result); + } + + // SERVER --- + + void Server::OnServiceSearchRequest(const ServerSocket& socket, const PDU& request, const Handler& handlerCb) + { + PDU::errorid result = PDU::Success; + std::list uuids; + uint16_t maxCount{}; + uint16_t offset = 0; + + request.InspectPayload([&](const Payload& payload) { + + // ServiceSearchRequest frame format: + // - ServiceSearchPattern (sequence of UUIDs) + // - MaximumServiceRecordCount (word) + // - ContinuationState + + // In this server implementation the continuation state is a word + // value containing the index of last service sent in the previous + // call. + + payload.Pop(use_descriptor, [&](const Payload& sequence) { + + // No count stored, need to read until end of the sequence... + + while (sequence.Available() >= 2 /* min UUID size */) { + UUID uuid; + + sequence.Pop(use_descriptor, uuid); + + TRACE_L5("ServiceSearchPattern[%d]=%s ('%s')", uuids.size(), uuid.ToString().c_str(), ClassID(uuid).Name().c_str()); + + if (uuid.IsValid() == true) { + uuids.push_back(uuid); + } + else { + TRACE_L1("SDP server: invalid UUID!"); + uuids.clear(); + break; + } + } + + if (sequence.Available() != 0) { + TRACE_L1("SDP server: Unexpected data ServiceSearchRequest payload!"); + // Let's continue anyway... + } + }); + + payload.Pop(maxCount); // 0 if truncated, 0 is error + + TRACE_L5("MaximumServiceRecordCount=%d", maxCount); + + if ((uuids.empty() == true) || (maxCount == 0)) { + TRACE_L1("SDP server: Invalid ServiceSearchRequest parameters!"); + result = PDU::InvalidRequestSyntax; + } + else if (payload.Available() >= sizeof(uint8_t)) { + uint8_t continuationSize; + payload.Pop(continuationSize); + + if (continuationSize == sizeof(uint16_t)) { + payload.Pop(offset); + + if (offset == 0) { + TRACE_L1("SDP server: Invalid ServiceSearchRequest continuation state (zero)!"); + result = PDU::InvalidContinuationState; + } + } + else if (continuationSize != 0) { + TRACE_L1("SDP server: Invalid ServiceSearchRequest continuation size!"); + result = PDU::InvalidContinuationState; + } + } + else { + TRACE_L1("SDP server: Truncated ServiceSearchRequest payload!"); + result = PDU::InvalidPduSize; + } + }); + + uint16_t count = 0; + std::list handles; + + if (result == PDU::Success) { + + // Sca all matching handles, even if can't fit them all in response, + // because the total number of matches needs to be returned. + + WithServiceTree([&](const Tree& tree) { + for (auto const& service : tree.Services()) { + for (auto const& uuid : uuids) { + + if (service.Search(uuid) == true) { + if ((count >= offset) && (handles.size() < maxCount)) { + handles.push_back(service.Handle()); + } + + count++; + } + } + } + }); + + if (offset > count) { + TRACE_L1("SDP server: Invalid ServiceSearchRequest continuation state!"); + result = PDU::InvalidContinuationState; + } + } + + if (result == PDU::Success) { + + // Cap the max count to the actual possible capacity. + maxCount = std::min(maxCount, (Capacity(socket, request) - (2 * sizeof(uint32_t)) / sizeof(uint32_t))); + TRACE_L5("capacity=%d handles", maxCount); + + handlerCb(PDU::ServiceSearchResponse, [&](Payload& payload) { + + // ServiceSearchResponse frame format: + // - TotalServiceRecordCount (word) + // - CurrentServiceRecordCount (word) + // - ServiceRecordHandleList (list of longs, not sequence) + // - ContinuationState + + payload.Push(count); + payload.Push(handles.size()); + payload.Push(handles); + + if ((offset + handles.size()) < count) { + // Not all sent yet! Store offset for continuation. + payload.Push(sizeof(uint16_t)); + payload.Push(offset + handles.size()); + } + else { + // All sent! + payload.Push(0); + } + + TRACE_L1("SDP server: ServiceSearch found %d matching service(s) and will reply with %d service(s)", count, handles.size()); + }); + } + else { + TRACE_L1("SDP server: ServiceSearchRequest failed!"); + handlerCb(result); + } + } + + /* private */ + void Server::InternalOnServiceSearchAttributeRequest(const PDU::pduid id, + const std::function&)>& inspectCb, + const ServerSocket& socket, + const PDU& request, + const Handler& handlerCb) + { + // Again, ServiceAttribute and ServiceSearchAttribute are very similar, + // so use one method to handle both. + + PDU::errorid result = PDU::Success; + + uint16_t maxByteCount{}; + std::list attributeRanges; + std::list handles; + uint16_t offset = 0; + + request.InspectPayload([&](const Payload& payload) { + + // ServiceAttributeRequest/ServiceSearch frame format: + // - ServiceRecordHandle (long) OR ServiceSearchPattern (sequence of UUIDs) + // - MaximumAttributeByteCount (word) + // - AttributeIDList (sequence of UUID ranges or UUIDs) + // - ContinuationState + + // In this server implementation the continuation state is a word + // value containing the index of last attribute sent in the previous + // call. + + if (inspectCb(payload, handles) == PDU::Success) { + + payload.Pop(maxByteCount); + TRACE_L5("MaximumAttributeByteCount=%d", maxByteCount); + + if (maxByteCount > 9 /* i.e. descriptors and one result entry */) { + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() >= (sizeof(uint16_t) + 1)) { + uint32_t range{}; + uint32_t size{}; + + sequence.Pop(use_descriptor, range, &size); + + if (size == sizeof(uint32_t)) { + // A range indeed. + TRACE_L5("AttributeIDList[%d]=%04x..%04x", attributeRanges.size(), (range >> 16), (range & 0xFFFF)); + + attributeRanges.push_back(range); + } + else if (size == sizeof(uint16_t)) { + // Not a range, but single 16-bit UUID. + TRACE_L5("AttributeIDList[%d]=%04x ('%s')", attributeRanges.size(), range, ClassID(UUID(range)).Name().c_str()); + + attributeRanges.push_back((static_cast(range) << 16) | static_cast(range)); + } + else { + TRACE_L1("SDP server: Invalid UUID list!"); + result = PDU::InvalidRequestSyntax; + } + } + + if (sequence.Available() != 0) { + TRACE_L1("SDP server: Unexpected data in Service(Search)AttributeRequest payload!"); + } + }); + } + + if ((maxByteCount < 9) || (attributeRanges.empty() == true)) { + TRACE_L1("SDP server: Invalid Service(Search)AttributeRequest parameters!"); + result = PDU::InvalidRequestSyntax; + } + else if (payload.Available() >= sizeof(uint8_t)) { + uint8_t continuationSize; + payload.Pop(continuationSize); + + if (continuationSize == sizeof(uint16_t)) { + payload.Pop(offset); + + if (offset == 0) { + TRACE_L1("SDP server: Invalid Service(Search)AttributeRequest continuation state (zero)!"); + result = PDU::InvalidContinuationState; + } + } + else if (continuationSize != 0) { + TRACE_L1("SDP server: Invalid Service(Search)AttributeRequest continuation size!"); + result = PDU::InvalidContinuationState; + } + } + else { + TRACE_L1("SDP server: Truncated Service(Search)AttributeRequest payload!"); + result = PDU::InvalidPduSize; + } + } + }); + + std::list> attributes; + uint16_t totalAttributes = 0; + + if (result == PDU::Success) { + + WithServiceTree([&](const Tree& tree) { + + for (auto const& handle : handles) { + const Service* service = tree.Find(handle); + + if (service != nullptr) { + attributes.emplace_back(SerializeAttributesByRange(*service, attributeRanges, offset, totalAttributes)); + } + } + }); + + if (result == PDU::Success) { + + // Cap the max count to the actual possible capacity. + maxByteCount = std::min(maxByteCount, (Capacity(socket, request) - sizeof(uint16_t))); + TRACE_L5("capacity=%d bytes", maxByteCount); + + handlerCb(id, [&](Payload& payload) { + + // ServiceAttributeResponse/ServiceSearchAttribute frame format: + // - AttributeListByteCount (word) + // - AttributeList (sequence of long:data pairs, where data size is dependant on the attribute and may be a sequence) + // - ContinuationState + + bool allDone = true; + uint16_t attributesWritten = 0; + + auto PushLists = [&](Payload& buffer) { + for (auto const& service : attributes) { + buffer.Push(use_descriptor, [&](Payload& sequence) { + for (auto const& attr : service) { + if ((sequence.Position() + attr.second.size() + sizeof(uint16_t)) < maxByteCount) { + + if (attr.second.size() != 0) { + sequence.Push([&](Payload& record) { + record.Push(use_descriptor, attr.first); + record.Push(attr.second); // Already packed as necessary, thus no use_descriptor here. + }); + + attributesWritten++; + } + } + else { + allDone = false; + break; + } + } + }); + + if (allDone == false) { + break; + } + } + }; + + payload.Push(use_length, [&](Payload& buffer) { + if (id == PDU::ServiceAttributeResponse) { + ASSERT(handles.size() == 1); + PushLists(buffer); + } + else { + // In case of ServiceSearchAtributes this is a list of lists. + buffer.Push(use_descriptor, [&](Payload& listBuffer) { + PushLists(listBuffer); + }); + } + }); + + if (allDone == false) { + payload.Push(sizeof(uint16_t)); + payload.Push(offset + attributesWritten); + } + else { + payload.Push(0); + } + + TRACE_L1("SDP server: Service(Search)Attribute found %d attribute(s) and will reply with %d attribute(s)", totalAttributes, attributesWritten); + }); + } + } + else { + TRACE_L1("SDP server: Service(Search)Attribute failed!"); + handlerCb(result); + } + } + + void Server::OnServiceAttributeRequest(const ServerSocket& socket, const PDU& request, const Handler& handlerCb) + { + InternalOnServiceSearchAttributeRequest(PDU::ServiceAttributeResponse, + [&](const Payload& payload, std::list& handles) -> PDU::errorid { + + // ServiceAttributeRequest frame format: + // - ServiceRecordHandle (long) + // - MaximumAttributeByteCount (word) + // - AttributeIDList (sequence of UUID ranges or UUIDs) + // - ContinuationState + + uint32_t handle{}; + payload.Pop(handle); // 0 if truncated + + TRACE_L1("ServiceRecordHandle=0x%08x", handle); + + if (handle != 0) { + // Easy, one handle is given, directly. + handles.push_back(handle); + } + + // MaximumAttributeByteCount, AttributeIDList and ContinuationState is handled by InternalOnServiceSearchAttributeRequest(). + + return (PDU::Success); + + }, socket, request, handlerCb); + } + + void Server::OnServiceSearchAttributeRequest(const ServerSocket& socket, const PDU& request, const Handler& handlerCb) + { + InternalOnServiceSearchAttributeRequest(PDU::ServiceSearchAttributeResponse, + [&](const Payload& payload, std::list& handles) -> PDU::errorid { + + // ServiceSearchAttribute frame format: + // - ServiceSearchPattern (sequence of UUIDs) + // - MaximumAttributeByteCount (word) + // - AttributeIDList (sequence of UUID ranges or UUIDs) + // - ContinuationState + + std::list uuids; + + // Pick up UUIDs of interest... + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() >= 2 /* min UUID size */) { + UUID uuid; + + sequence.Pop(use_descriptor, uuid); + + TRACE_L1("ServiceSearchPattern[%d]=%s '%s'", uuids.size(), uuid.ToString().c_str(), ClassID(uuid).Name().c_str()); + + if (uuid.IsValid() == true) { + uuids.push_back(uuid); + } + else { + TRACE_L1("SDP server: Invalid UUID in ServiceSearchRequest payload!"); + } + } + + if (sequence.Available() != 0) { + TRACE_L1("SDP server: Unexpected data ServiceSearchRequest payload!"); + } + }); + + if (uuids.empty() == false) { + // Now have to find the handles that have anything to do with the UUIDs recieved. + + WithServiceTree([&](const Tree& tree) { + for (auto const& service : tree.Services()) { + for (auto const& uuid : uuids) { + if (service.Search(uuid) == true) { + handles.push_back(service.Handle()); + } + } + } + }); + } + + TRACE_L1("SDP server: Found %d service(s) matching the search patterns", handles.size()); + + return (uuids.empty() == true? PDU::InvalidRequestSyntax : PDU::Success); + + }, socket, request, handlerCb); + } + + /* private */ + std::map Server::SerializeAttributesByRange(const Service& service, const std::list& attributeRanges, const uint16_t offset, uint16_t& count) const + { + std::map attributes; + + for (uint32_t range : attributeRanges) { + const uint16_t standardLeft = std::max(Service::AttributeDescriptor::ServiceRecordHandle, (range >> 16)); + const uint16_t standardRight = std::min(Service::AttributeDescriptor::IconURL, (range & 0xFFFF)); + + // Universal attributes. + for (uint16_t id = standardLeft; id <= standardRight; id++) { + Buffer buffer = service.Serialize(id); + + if (buffer.empty() == false) { + if (count >= offset) { + attributes.emplace(id, std::move(buffer)); + } + + count++; + } + } + + // custom attributes + for (auto const& attr : service.Attributes()) { + if ((attr.Id() >= std::max(0x100, (range >> 16))) && (attr.Id() <= (range & 0xFFFF))) { + if (count >= offset) { + attributes.emplace(attr.Id(), attr.Value()); + } + + count++; + } + } + } + + return (attributes); + } + +} // namespace SDP + +} // namespace Bluetooth + +} \ No newline at end of file diff --git a/Source/extensions/bluetooth/audio/SDPProfile.h b/Source/extensions/bluetooth/audio/SDPProfile.h new file mode 100644 index 000000000..55065c393 --- /dev/null +++ b/Source/extensions/bluetooth/audio/SDPProfile.h @@ -0,0 +1,1278 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "SDPSocket.h" + +#include + + +namespace Thunder { + +namespace Bluetooth { + +namespace SDP { + + static constexpr uint16_t CHARSET_US_ASCII = 3; + static constexpr uint16_t CHARSET_UTF8 = 106; + + class EXTERNAL ClassID { + public: + enum id : uint16_t { + Undefined = 0x0000, + + // Protocols + SDP = 0x0001, + UDP = 0x0002, + RFCOMM = 0x0003, + TCP = 0x0004, + TCS_BIN = 0x0005, + TCS_AT = 0x0006, + ATT = 0x0007, + OBEX = 0x0008, + IP = 0x0009, + FTP = 0x000a, + HTTP = 0x000c, + WSP = 0x000e, + BNEP = 0x000f, + UPNP = 0x0010, + HIDP = 0x0011, + HCRP_CTRL = 0x0012, + HCRP_DATA = 0x0014, + HCRP_NOTE = 0x0016, + AVCTP = 0x0017, + AVDTP = 0x0019, + CMTP = 0x001b, + UDI = 0x001d, + MCAP_CTRL = 0x001e, + MCAP_DATA = 0x001f, + L2CAP = 0x0100, + + // SDP itself + ServiceDiscoveryServer = 0x1000, // Service + BrowseGroupDescriptor = 0x1001, + PublicBrowseRoot = 0x1002, + + // Services and Profiles + SerialPort = 0x1101, // Service + Profile + LANAccessUsingPPP = 0x1102, // Service + Profile + DialupNetworking = 0x1103, // Service + Profile + IrMCSync = 0x1104, // Service + Profile + OBEXObjectPush = 0x1105, // Service + Profile + OBEXFileTransfer = 0x1106, // Service + Profile + IrMCSyncCommand = 0x1107, // Service + HeadsetHSP = 0x1108, // Service + Profile + CordlessTelephony = 0x1109, // Service + Profile + AudioSource = 0x110A, // Service + AudioSink = 0x110B, // Service + AVRemoteControlTarget = 0x110C, // Service + AdvancedAudioDistribution = 0x110D, // Profile + AVRemoteControl = 0x110E, // Service + Profile + AVRemoteControlController = 0x110F, // Service + Intercom = 0x1110, // Service + Profile + Fax = 0x1111, // Service + Profile + HeadsetAudioGateway = 0x1112, // Service + WAP = 0x1113, // Service + WAPClient = 0x1114, // Service + PANU = 0x1115, // Service + Profile + NAP = 0x1116, // Service + Profile + GN = 0x1117, // Service + Profile + DirectPrinting = 0x1118, // Service + ReferencePrinting = 0x1119, // Service + BasicImagingProfile = 0x111A, // Profile + ImagingResponder = 0x111B, // Service + ImagingAutomaticArchive = 0x111C, // Service + ImagingReferencedObjects = 0x111D, // Service + Handsfree = 0x111E, // Service + Profile + HandsfreeAudioGateway = 0x111F, // Service + DirectPrintingReferenceObjects = 0x1120, // Service + ReflectedUI = 0x1121, // Service + BasicPrinting = 0x1122, // Profile + PrintingStatus = 0x1123, // Service + HumanInterfaceDeviceService = 0x1124, // Service + Profile + HardcopyCableReplacement = 0x1125, // Profile + HCRPrint = 0x1126, // Service + HCRScan = 0x1127, // Service + CommonISDNAccess = 0x1128, // Service + Profile + SIMAccess = 0x112D, // Service + Profile + PhonebookAccessPCE = 0x112E, // Service + PhonebookAccessPSE = 0x112F, // Service + PhonebookAccess = 0x1130, // Profile + HeadsetHS = 0x1131, // Service + MessageAccessServer = 0x1132, // Service + MessageNotificationServer = 0x1133, // Service + MessageAccess = 0x1134, // Profile + GNSS = 0x1135, // Profile + GNSSServer = 0x1136, // Service + ThreeDDisplay = 0x1137, // Service + ThreeDGlasses = 0x1138, // Service + ThreeDSynchronisation = 0x1339, // Profile + MPS = 0x113A, // Profile + MPSSC = 0x113B, // Service + CTNAccessService = 0x113C, // Service + CTNNotificationService = 0x113D, // Service + CTN = 0x113E, // Profile + PnPInformation = 0x1200, // Service + GenericNetworking = 0x1201, // Service + GenericFileTransfer = 0x1202, // Service + GenericAudio = 0x1203, // Service + GenericTelephony = 0x1204, // Service + UPNPService = 0x1205, // Service + UPNPIPService = 0x1206, // Service + ESDPUPNPIPPAN = 0x1300, // Service + ESDPUPNPIPLAP = 0x1301, // Service + ESDPUPNPL2CAP = 0x1302, // Service + VideoSource = 0x1303, // Service + VideoSink = 0x1304, // Service + VideoDistribution = 0x1305, // Profile + HDP = 0x1400, // Profile + HDPSource = 0x1401, // Service + HDPSink = 0x1402 // Service + }; + + public: + ClassID() + : _id() + { + } + ClassID(const ClassID& other) + : _id(other._id) + { + } + ClassID(const id& classId) + : _id(UUID(classId)) + { + } + ClassID(const UUID& uuid) + : _id(uuid) + { + } + ~ClassID() = default; + + public: + ClassID& operator=(const ClassID& rhs) + { + _id = rhs._id; + return (*this); + } + bool operator==(const ClassID& rhs) const + { + return (_id == rhs._id); + } + bool operator!=(const ClassID& rhs) const + { + return !(*this == rhs); + } + bool operator<(const ClassID& rhs) const + { + return (_id < rhs._id); + } + + public: + const UUID& Type() const + { + return (_id); + } + const string Name() const + { + string name; + if (_id.HasShort() == true) { + id input = static_cast(_id.Short()); + Core::EnumerateType value(input); + name = (value.IsSet() == true? string(value.Data()) : _id.ToString(false)); + } + if (name.empty() == true) { + name = _id.ToString(); + } + return (name); + } + + private: + UUID _id; + }; // class ClassID + + class EXTERNAL Service { + public: + class EXTERNAL AttributeDescriptor { + public: + // universal attributes + enum id : uint16_t { + // required + ServiceRecordHandle = 0x0000, + ServiceClassIDList = 0x0001, + // optional + ServiceRecordState = 0x0002, + ServiceID = 0x0003, + ProtocolDescriptorList = 0x0004, + BrowseGroupList = 0x0005, + LanguageBaseAttributeIDList = 0x0006, + ServiceInfoTimeToLive = 0x0007, + ServiceAvailability = 0x0008, + ProfileDescriptorList = 0x0009, + DocumentationURL = 0x000a, + ClientExecutableURL = 0x000b, + IconURL = 0x000c, + }; + + static constexpr uint16_t ServiceNameOffset = 0; + static constexpr uint16_t ServiceDescriptionOffset = 1; + static constexpr uint16_t ProviderNameOffset = 2; + + // specific to Advanced Audio Distribution + enum class a2dp : uint16_t { + SupportedFeatures = 0x0311 + }; + + // specfic to AVRemoteControl profile + enum class avcrp : uint16_t { + SupportedFeatures = 0x0311 + }; + + // specific to Basic Imaging profile + enum class bip : uint16_t { + GoepL2capPsm = 0x0200, + SupportedCapabilities = 0x0300, + SupportedFeatures = 0x0311, + SupportedFunctions = 0x0312, + TotalImagingDataCapacity = 0x0313 + }; + + // specific to PnPInformation profile + enum class did : uint16_t { + SpecificationID = 0x0200, + VendorID = 0x0201, + ProductID = 0x0202, + Version = 0x0203, + PrimaryRecord = 0x0204, + VendorIDSource = 0x0205 + }; + + public: + AttributeDescriptor() = delete; + AttributeDescriptor(const AttributeDescriptor&) = delete; + AttributeDescriptor& operator=(const AttributeDescriptor&) = delete; + + template + AttributeDescriptor(const uint16_t id, T&& value) + : _id(id) + , _value(std::forward(value)) + { + TRACE_L5("AttributeDescriptor: adding raw attribute 0x%04x", id); + } + + ~AttributeDescriptor() = default; + + public: + bool operator==(const AttributeDescriptor& rhs) const + { + return (_id == rhs._id); + } + bool operator!=(const AttributeDescriptor& rhs) const + { + return !(*this == rhs); + } + bool operator<(const AttributeDescriptor& rhs) const + { + return (_id < rhs._id); + } + + public: + uint32_t Id() const + { + return (_id); + } + const Buffer& Value() const + { + return (_value); + } + string Name() const + { + Core::EnumerateType value(_id); + string name = (value.IsSet() == true? string(value.Data()) : _T("")); + return (name); + } + + private: + uint16_t _id; + Buffer _value; + }; // class AttributeDescriptor + + struct Data { + template + class EXTERNAL Element { + public: + Element() = delete; + Element(const Element&) = default; + Element& operator=(const Element&) = default; + + Element(T&& data) + : _data(std::move(data)) + { + } + Element(const T& data) + : _data(data) + { + } + Element(const Buffer& buffer) + : _data() + { + const Payload payload(buffer); + if (payload.Available() > 0) { + payload.Pop(use_descriptor, _data); + } + } + ~Element() = default; + + public: + const T& Value() const + { + return (_data); + } + + public: + operator Buffer() const + { + uint8_t scratchpad[256]; + Payload payload(scratchpad, sizeof(scratchpad), 0); + payload.Push(use_descriptor, _data); + return (payload); + } + + private: + T _data; + }; // class Element + }; // struct Data + + struct Protocol { + public: + class EXTERNAL L2CAP : public Data::Element { + public: + using Element::Element; + L2CAP(const L2CAP&) = default; + L2CAP& operator=(const L2CAP&) = default; + ~L2CAP() = default; + + public: + uint16_t PSM() const + { + return (Value()); + } + }; // class L2CAP + + class EXTERNAL AVDTP : public Data::Element { + public: + using Element::Element; + AVDTP(const AVDTP&) = default; + AVDTP& operator=(const AVDTP&) = default; + ~AVDTP() = default; + + public: + uint16_t Version() const + { + return (Value()); + } + }; // class AVDTP + }; // struct Protocol + + struct Attribute { + class EXTERNAL ServiceRecordHandle { + public: + static constexpr auto type = AttributeDescriptor::ServiceRecordHandle; + + public: + ServiceRecordHandle() + : _handle(0) + { + } + ServiceRecordHandle(const uint32_t handle) + : _handle(handle) + { + } + ServiceRecordHandle(const uint8_t buffer[], const uint16_t size) + { + uint32_t handle{}; + const Payload payload(buffer, size); + payload.Pop(use_descriptor, handle); + Handle(handle); + } + ServiceRecordHandle(const ServiceRecordHandle&) = delete; + ServiceRecordHandle& operator=(const ServiceRecordHandle&) = delete; + ~ServiceRecordHandle() = default; + + public: + operator Buffer() const + { + uint8_t scratchPad[8]; + Payload payload(scratchPad, sizeof(scratchPad), 0); + payload.Push(use_descriptor, _handle); + return (payload); + } + + public: + void Handle(const uint32_t handle) + { + TRACE_L5("ServiceRecordHandle: handle 0x%08x", handle); + _handle = handle; + } + uint32_t Handle() const + { + return (_handle); + } + + private: + uint32_t _handle; + }; // class ServiceRecordHandle + + class EXTERNAL ServiceClassIDList { + public: + static constexpr auto type = AttributeDescriptor::ServiceClassIDList; + + public: + ServiceClassIDList() + : _classes() + { + } + ServiceClassIDList(const uint8_t buffer[], const uint16_t size) + { + const Payload payload(buffer, size); + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() > 0) { + UUID uuid; + sequence.Pop(use_descriptor, uuid); + Add(uuid); + } + }); + } + ServiceClassIDList(const ServiceRecordHandle&) = delete; + ServiceClassIDList& operator=(const ServiceRecordHandle&) = delete; + ~ServiceClassIDList() = default; + + public: + operator Buffer() const + { + uint8_t scratchPad[256]; + Payload payload(scratchPad, sizeof(scratchPad), 0); + payload.Push(use_descriptor, [this](Payload& sequence) { + for (auto const& classId : Classes()) { + sequence.Push(use_descriptor, classId.Type()); + } + }); + + return (payload); + } + + public: + void Add(const ClassID& classId) + { + TRACE_L5("ServiceClassIDList: Added class %s '%s'", classId.Type().ToString().c_str(), classId.Name().c_str()); + _classes.emplace(classId); + } + const std::set& Classes() const + { + return (_classes); + } + bool HasID(const UUID& id) const + { + return (_classes.find(id) != _classes.cend()); + } + + private: + std::set _classes; + }; // class ServiceClassIDList + + class EXTERNAL ProtocolDescriptorList { + public: + static constexpr auto type = AttributeDescriptor::ProtocolDescriptorList; + + public: + ProtocolDescriptorList() + : _protocols() + { + } + ProtocolDescriptorList(const uint8_t buffer[], const uint16_t size) + { + const Payload payload(buffer, size); + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() > 0) { + sequence.Pop(use_descriptor, [&](const Payload& record) { + UUID uuid; + Buffer params; + record.Pop(use_descriptor, uuid); + record.Pop(params, record.Available()); // No descriptor! Just take everything what's left in this record. + Add(uuid, params); + }); + } + }); + } + ProtocolDescriptorList(const ProtocolDescriptorList&) = delete; + ProtocolDescriptorList& operator=(const ProtocolDescriptorList&) = delete; + ~ProtocolDescriptorList() = default; + + public: + operator Buffer() const + { + uint8_t scratchPad[256]; + Payload payload(scratchPad, sizeof(scratchPad), 0); + payload.Push(use_descriptor, [this](Payload& sequence) { + for (auto const& kv : Protocols()) { + sequence.Push(use_descriptor, [&kv](Payload& record) { + record.Push(use_descriptor, kv.first.Type()); + record.Push(kv.second); // no descriptor here! + }); + } + }); + + return (payload); + } + + public: + template + void Add(const ClassID& id, T&& data) + { + TRACE_L5("ProtocolDescriptorList: added %s '%s'", id.Type().ToString().c_str(), id.Name().c_str()); + _protocols.emplace(id, std::forward(data)); + } + const std::map& Protocols() const + { + return (_protocols); + } + const Buffer* Protocol(const UUID& id) const + { + auto const& it = _protocols.find(id); + if (it != _protocols.cend()) { + return (&(*it).second); + } else { + return (nullptr); + } + } + + private: + std::map _protocols; + }; // class ProtocolDescriptorList + + class EXTERNAL BrowseGroupList : public ServiceClassIDList { + public: + static constexpr auto type = AttributeDescriptor::BrowseGroupList; + + public: + using ServiceClassIDList::ServiceClassIDList; + BrowseGroupList(const BrowseGroupList&) = delete; + BrowseGroupList& operator=(const BrowseGroupList&) = delete; + ~BrowseGroupList() = default; + }; // class BrowseGroupList + + class LanguageBaseAttributeIDList { + public: + static constexpr auto type = AttributeDescriptor::LanguageBaseAttributeIDList; + + public: + class Triplet { + public: + Triplet() = delete; + Triplet(const Triplet&) = default; + Triplet& operator=(const Triplet&) = default; + + Triplet(const uint16_t language, const uint16_t charset, const uint16_t base) + : _language(language) + , _charset(charset) + , _base(base) + { + } + + ~Triplet() = default; + + public: + uint16_t Language() const + { + return (_language); + } + uint16_t Charset() const + { + return (_charset); + } + uint16_t Base() const + { + return (_base); + } + + private: + uint16_t _language; + uint16_t _charset; + uint16_t _base; + }; + + public: + LanguageBaseAttributeIDList() + : _languageBases() + , _freeBase(0x100) + { + } + LanguageBaseAttributeIDList(const uint8_t buffer[], const uint16_t size) + { + const Payload payload(buffer, size); + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() > 0) { + uint16_t lang = 0; + uint16_t charset = 0; + uint16_t base = 0; + + sequence.Pop(use_descriptor, lang); + sequence.Pop(use_descriptor, charset); + sequence.Pop(use_descriptor, base); + + Add(lang, charset, base); + } + }); + } + LanguageBaseAttributeIDList(const LanguageBaseAttributeIDList&) = delete; + LanguageBaseAttributeIDList& operator=(const LanguageBaseAttributeIDList&) = delete; + ~LanguageBaseAttributeIDList() = default; + + public: + operator Buffer() const + { + uint8_t scratchPad[256]; + Payload payload(scratchPad, sizeof(scratchPad), 0); + payload.Push(use_descriptor, [this](Payload& sequence) { + for (auto const& entry : LanguageBases()) { + sequence.Push(use_descriptor, entry.Language()); + sequence.Push(use_descriptor, entry.Charset()); + sequence.Push(use_descriptor, entry.Base()); + } + }); + + return (payload); + } + + public: + void Add(const uint16_t language, const uint16_t charset, const uint16_t base) + { + TRACE_L5("LanguageBaseAttributeIDList: added 0x%04x 0x%04x 0x%04x", language, charset, base); + _languageBases.emplace_back(language, charset, base); + _freeBase = base + 3; + } + void Add(const string& language, const uint16_t charset, const uint16_t base) + { + ASSERT(language.size() == 2); + Add(ToCode(language), charset, base); + } + uint16_t Add(const string& language = _T("en"), const uint16_t charset = CHARSET_US_ASCII) + { + const uint16_t base = _freeBase; + Add(language, charset, base); + return (base); + } + + public: + const std::list& LanguageBases() const + { + return (_languageBases); + } + uint16_t LanguageBase(const uint16_t language, const uint16_t charset) const + { + auto const& it = std::find_if(_languageBases.cbegin(), _languageBases.cend(), + [&](const Triplet& entry) { return ((entry.Language() == language) && (entry.Charset() == charset)); }); + + if (it != _languageBases.cend()) { + return ((*it).Base()); + } else { + return (0); + } + } + uint16_t LanguageBase(const uint16_t language) const + { + auto const& it = std::find_if(_languageBases.cbegin(), _languageBases.cend(), + [&](const Triplet& entry) { return (entry.Language() == language); }); + + if (it != _languageBases.cend()) { + return ((*it).Base()); + } else { + return (0); + } + } + uint16_t LanguageBase(const string& language, const uint16_t charset) const + { + return (LanguageBase(ToCode(language), charset)); + } + uint16_t LanguageBase(const string& language) const + { + return (LanguageBase(ToCode(language))); + } + + private: + static uint16_t ToCode(const string& language) + { + return ((static_cast(language[0]) << 8) | language[1]); + } + + private: + std::list _languageBases; + uint16_t _freeBase; + }; // class LanguageBaseAttributeIDList + + class EXTERNAL ProfileDescriptorList { + public: + static constexpr auto type = AttributeDescriptor::ProfileDescriptorList; + + public: + class Data { + public: + Data() = delete; + Data(const Data&) = delete; + Data& operator=(const Data&) = delete; + + Data(const uint16_t version) + : _version(version) + { + } + + ~Data() = default; + + public: + uint16_t Version() const + { + return (_version); + } + + private: + uint16_t _version; + }; // class Data + + public: + ProfileDescriptorList() + : _profiles() + { + } + ProfileDescriptorList(const uint8_t buffer[], const uint16_t size) + { + const Payload payload(buffer, size); + payload.Pop(use_descriptor, [&](const Payload& sequence) { + while (sequence.Available() > 0) { + sequence.Pop(use_descriptor, [&](const Payload& record) { + UUID uuid; + uint16_t version{}; + record.Pop(use_descriptor, uuid); + record.Pop(use_descriptor, version); + Add(uuid, version); + }); + } + }); + } + ProfileDescriptorList(const ProfileDescriptorList&) = delete; + ProfileDescriptorList& operator=(const ProfileDescriptorList&) = delete; + ~ProfileDescriptorList() = default; + + public: + operator Buffer() const + { + uint8_t scratchPad[256]; + Payload payload(scratchPad, sizeof(scratchPad), 0); + payload.Push(use_descriptor, [this](Payload& sequence) { + for (auto const& kv : Profiles()) { + sequence.Push(use_descriptor, [&kv](Payload& record) { + record.Push(use_descriptor, kv.first.Type()); + record.Push(use_descriptor, kv.second.Version()); + }); + } + }); + + return (payload); + } + + public: + void Add(const ClassID& id, const uint16_t version) + { + TRACE_L5("ProfileDescriptorList: added %s '%s'", id.Type().ToString().c_str(), id.Name().c_str()); + _profiles.emplace(id, version); + } + const std::map& Profiles() const + { + return (_profiles); + } + const Data* Profile(const UUID& id) const + { + auto const& it = _profiles.find(id); + if (it != _profiles.cend()) { + return (&(*it).second); + } else { + return (nullptr); + } + } + + private: + std::map _profiles; + }; // class ProfileDescriptorList + }; // struct Attribute + + public: + Service() = delete; + Service(const Service&) = delete; + Service& operator=(const Service&) = delete; + + Service(const uint32_t handle) + : _enabled(false) + , _serviceRecordHandle(nullptr) + , _serviceClassIDList(nullptr) + , _protocolDescriptorList(nullptr) + , _browseGroupList(nullptr) + , _languageBaseAttributeIDList(nullptr) + , _profileDescriptorList(nullptr) + { + if (handle != 0) { + ServiceRecordHandle()->Handle(handle); + } + } + + ~Service() + { + delete _serviceRecordHandle; + delete _serviceClassIDList; + delete _protocolDescriptorList; + delete _browseGroupList; + delete _languageBaseAttributeIDList; + delete _profileDescriptorList; + } + public: + void Deserialize(const uint16_t id, const Buffer& buffer) + { + Add(id, buffer, _serviceRecordHandle, + _serviceClassIDList, + _protocolDescriptorList, + _browseGroupList, + _languageBaseAttributeIDList, + _profileDescriptorList); + } + Buffer Serialize(const uint16_t id) const + { + return (Descriptor(id, _serviceRecordHandle, + _serviceClassIDList, + _protocolDescriptorList, + _browseGroupList, + _languageBaseAttributeIDList, + _profileDescriptorList)); + } + + public: + uint32_t Handle() const + { + return (ServiceRecordHandle() == nullptr? 0 : ServiceRecordHandle()->Handle()); + } + bool HasClassID(const UUID& id) const + { + return (ServiceClassIDList() == nullptr? false : ServiceClassIDList()->HasID(id)); + } + bool IsInBrowseGroup(const UUID& id) const + { + return (BrowseGroupList() == nullptr? false : BrowseGroupList()->HasID(id)); + } + const Buffer* Protocol(const UUID& id) const + { + return (ProtocolDescriptorList() == nullptr? nullptr : ProtocolDescriptorList()->Protocol(id)); + } + const Attribute::ProfileDescriptorList::Data* Profile(const UUID& id) const + { + return (ProfileDescriptorList() == nullptr? 0 : ProfileDescriptorList()->Profile(id)); + } + template::value, int>::type = 0> + const Buffer* Attribute(const TYPE id) const + { + // enum class is not implicitly convertible to its underlying type + return (Attribute(static_cast(id))); + } + const Buffer* Attribute(const uint16_t id) const + { + auto const& it = std::find_if(_attributes.cbegin(), _attributes.cend(), [&](const AttributeDescriptor& attr) { return (attr.Id() == id); }); + return (it == _attributes.cend()? nullptr : &(*it).Value()); + } + bool Search(const UUID& id) const + { + return ((IsEnabled() == true) && ((HasClassID(id) == true) || (IsInBrowseGroup(id) == true) || (Protocol(id) != nullptr) || (Profile(id) != 0))); + } + string Name() const + { + string name{}; + + if (LanguageBaseAttributeIDList() != nullptr) { + uint16_t lb = LanguageBaseAttributeIDList()->LanguageBase("en", CHARSET_US_ASCII); + if (lb == 0) { + lb = LanguageBaseAttributeIDList()->LanguageBase("en", CHARSET_UTF8); + } + + if (lb != 0) { + const Buffer* buffer = Attribute(lb + AttributeDescriptor::ServiceNameOffset); + if (buffer != nullptr) { + name = Data::Element(*buffer).Value(); + } + } + } + + return (name); + } + bool Metadata(string& name, string& description, string& provider, const string& language = _T("en"), const uint16_t charset = CHARSET_US_ASCII) const + { + bool result = false; + + if (LanguageBaseAttributeIDList() != nullptr) { + uint16_t lb = LanguageBaseAttributeIDList()->LanguageBase(language, charset); + if (lb != 0) { + const Buffer* buffer = Attribute(lb + AttributeDescriptor::ServiceNameOffset); + if (buffer != nullptr) { + name = Data::Element(*buffer).Value(); + } + + buffer = Attribute(lb + AttributeDescriptor::ServiceDescriptionOffset); + if (buffer != nullptr) { + description = Data::Element(*buffer).Value(); + } + + buffer = Attribute(lb + AttributeDescriptor::ProviderNameOffset); + if (buffer != nullptr) { + provider = Data::Element(*buffer).Value(); + } + + result = true; + } + } + + return (result); + } + + public: + void Enable(const bool enable) + { + _enabled = enable; + } + bool IsEnabled() const + { + return (_enabled); + } + Attribute::ServiceRecordHandle* ServiceRecordHandle() + { + if (_serviceRecordHandle == nullptr) { + _serviceRecordHandle = new Attribute::ServiceRecordHandle(); + ASSERT(_serviceRecordHandle != nullptr); + } + return (_serviceRecordHandle); + } + const Attribute::ServiceRecordHandle* ServiceRecordHandle() const + { + return (_serviceRecordHandle); + } + Attribute::ServiceClassIDList* ServiceClassIDList() + { + if (_serviceClassIDList == nullptr) { + _serviceClassIDList = new Attribute::ServiceClassIDList(); + ASSERT(_serviceClassIDList != nullptr); + } + return (_serviceClassIDList); + } + const Attribute::ServiceClassIDList* ServiceClassIDList() const + { + return (_serviceClassIDList); + } + Attribute::ProtocolDescriptorList* ProtocolDescriptorList() + { + if (_protocolDescriptorList == nullptr) { + _protocolDescriptorList = new Attribute::ProtocolDescriptorList(); + ASSERT(_protocolDescriptorList != nullptr); + } + return (_protocolDescriptorList); + } + const Attribute::ProtocolDescriptorList* ProtocolDescriptorList() const + { + return (_protocolDescriptorList); + } + Attribute::BrowseGroupList* BrowseGroupList() + { + if (_browseGroupList == nullptr) { + _browseGroupList = new Attribute::BrowseGroupList(); + ASSERT(_browseGroupList != nullptr); + } + return (_browseGroupList); + } + const Attribute::BrowseGroupList* BrowseGroupList() const + { + return (_browseGroupList); + } + Attribute::LanguageBaseAttributeIDList* LanguageBaseAttributeIDList() + { + if (_languageBaseAttributeIDList == nullptr) { + _languageBaseAttributeIDList = new Attribute::LanguageBaseAttributeIDList(); + ASSERT(_languageBaseAttributeIDList != nullptr); + } + return (_languageBaseAttributeIDList); + } + const Attribute::LanguageBaseAttributeIDList* LanguageBaseAttributeIDList() const + { + return (_languageBaseAttributeIDList); + } + Attribute::ProfileDescriptorList* ProfileDescriptorList() + { + if (_profileDescriptorList == nullptr) { + _profileDescriptorList = new Attribute::ProfileDescriptorList(); + ASSERT(_profileDescriptorList != nullptr); + } + return (_profileDescriptorList); + } + const Attribute::ProfileDescriptorList* ProfileDescriptorList() const + { + return (_profileDescriptorList); + } + + public: + std::set& Attributes() + { + return (_attributes); + } + const std::set& Attributes() const + { + return (_attributes); + } + + public: + void Add(const uint16_t id, const Buffer& buffer) + { + if (buffer.empty() == false) { + _attributes.emplace(id, buffer); + } + } + template::value, int>::type = 0> + void Add(const TYPE id, const Buffer& buffer) + { + // enum class is not implicitly convertible to its underlying type + Add(static_cast(id), buffer); + } + template + void Description(const std::string& name, const std::string& description = {}, const std::string& provider = {}, Args... args) + { + uint16_t id = LanguageBaseAttributeIDList()->Add(args...); + + if (name.empty() == false) { + Add(id + AttributeDescriptor::ServiceNameOffset, Data::Element(name)); + } + + if (description.empty() == false) { + Add(id + AttributeDescriptor::ServiceDescriptionOffset, Data::Element(description)); + } + + if (provider.empty() == false) { + Add(id + AttributeDescriptor::ProviderNameOffset, Data::Element(provider)); + } + } + + private: + template + void Add(const uint16_t id, const Buffer& buffer, T& attribute, Ts&... attributes) + { + if (buffer.size() != 0) { + using Attribute = typename std::remove_pointer::type; + + if (id == Attribute::type) { + if (attribute == nullptr) { + attribute = new Attribute(buffer.data(), buffer.size()); + ASSERT(attribute != nullptr); + +#ifdef __DEBUG__ + // In debug, store all the attributes in raw form for inspection. + Add(id, buffer); +#endif + } + } else { + Add(id, buffer, attributes...); + // Once the attribute list for deserialisation is recursively exhausted + // this will fall back to storing raw attributes. + } + } + } + + private: + template + Buffer Descriptor(const uint16_t id, const T attribute, const Ts... attributes) const + { + using Attribute = typename std::remove_pointer::type; + + if (id == Attribute::type) { + return (attribute != nullptr? *attribute : Buffer{}); + } else { + return Descriptor(id, attributes...); + } + } + Buffer Descriptor(const uint16_t) const + { + // Just to stop the recursive attribute serialisation. + return {}; + } + + private: + bool _enabled; + Attribute::ServiceRecordHandle* _serviceRecordHandle; + Attribute::ServiceClassIDList* _serviceClassIDList; + Attribute::ProtocolDescriptorList* _protocolDescriptorList; + Attribute::BrowseGroupList* _browseGroupList; + Attribute::LanguageBaseAttributeIDList* _languageBaseAttributeIDList; + Attribute::ProfileDescriptorList* _profileDescriptorList; + std::set _attributes; + }; // class Service + + class EXTERNAL Tree { + public: + Tree(const Tree&) = delete; + Tree& operator=(const Tree&) = delete; + ~Tree() = default; + + Tree() + : _services() + { + } + + public: + const std::list& Services() const + { + return (_services); + } + const SDP::Service* Find(const uint32_t handle) const + { + auto const& it = std::find_if(_services.cbegin(), _services.cend(), [&](const Service& s) { return (s.Handle() == handle); }); + return (it == _services.cend()? nullptr : &(*it)); + } + Service& Add(const uint32_t handle = 0) + { + _services.emplace_back(handle == 0? (0x10000 + _services.size()) : handle); + Service& added = _services.back(); + return (added); + } + + protected: + std::list _services; + }; // class Tree + + class EXTERNAL Client { + public: + Client() = delete; + Client(const Client&) = delete; + Client& operator=(const Client&) = delete; + ~Client() = default; + + Client(ClientSocket& socket) + : _socket(socket) + { + } + + public: + uint32_t Discover(const std::list& services, Tree& tree) const; + + public: + uint32_t ServiceSearch(const std::list& services, std::vector& outHandles) const; + + uint32_t ServiceAttribute(const uint32_t serviceHandle, + const std::list& attributeIdRanges, + std::list>& outAttributes) const; + + uint32_t ServiceSearchAttribute(const std::list& services, + const std::list& attributeIdRanges, + std::list>& outAttributes) const; + + private: + uint32_t InternalServiceAttribute(const PDU::pduid id, + const std::function& buildCb, + const std::list& attributeIdRanges, + std::list>& outAttributes) const; + + private: + uint32_t Execute(ClientSocket::Command& cmd, const Payload::Inspector& inspectorCb = nullptr) const; + + private: + uint16_t Capacity(const PDU& pdu) const + { + const uint16_t bufferSize = std::min(pdu.Capacity(), (_socket.InputMTU() - PDU::HeaderSize)); + ASSERT(bufferSize >= (1 + PDU::MaxContinuationSize)); + + return (bufferSize - (1 + PDU::MaxContinuationSize)); + } + + private: + ClientSocket& _socket; + }; // class Client + + class EXTERNAL Server { + using Handler = ServerSocket::ResponseHandler; + + public: + Server() = default; + Server(const Server&) = delete; + Server& operator=(const Server&) = delete; + virtual ~Server() = default; + + public: + virtual void WithServiceTree(const std::function& inspectCb) = 0; + + public: + void OnPDU(const ServerSocket& socket, const PDU& pdu, const Handler& handler) + { + switch (pdu.Type()) { + case PDU::ServiceSearchRequest: + OnServiceSearchRequest(socket, pdu, handler); + break; + case PDU::ServiceAttributeRequest: + OnServiceAttributeRequest(socket, pdu, handler); + break; + case PDU::ServiceSearchAttributeRequest: + OnServiceSearchAttributeRequest(socket, pdu, handler); + break; + default: + TRACE_L1("SDP server: Usupported PDU %d", pdu.Type()); + handler(PDU::InvalidRequestSyntax); + break; + } + } + + private: + void OnServiceSearchRequest(const ServerSocket& socket, const PDU& pdu, const Handler& handler); + void OnServiceAttributeRequest(const ServerSocket& socket, const PDU& pdu, const Handler& handler); + void OnServiceSearchAttributeRequest(const ServerSocket& socket, const PDU& pdu, const Handler& handler); + + private: + void InternalOnServiceSearchAttributeRequest(const PDU::pduid id, + const std::function&)>& inspectCb, + const ServerSocket& socket, + const PDU& request, + const Handler& handlerCb); + + std::map SerializeAttributesByRange(const Service& service, const std::list& attributeRanges, + const uint16_t offset, uint16_t& count) const; + + + private: + uint16_t Capacity(const ServerSocket& socket, const PDU& pdu) const + { + const uint16_t bufferSize = std::min(pdu.Capacity(), (socket.OutputMTU() - PDU::HeaderSize)); + ASSERT(bufferSize >= (1 + PDU::MaxContinuationSize)); + + return (bufferSize - (1 + PDU::MaxContinuationSize)); + } + + }; // class Server + +} // namespace SDP + +} // namespace Bluetooth + +} \ No newline at end of file diff --git a/Source/extensions/bluetooth/audio/SDPSocket.cpp b/Source/extensions/bluetooth/audio/SDPSocket.cpp new file mode 100644 index 000000000..da353b9f2 --- /dev/null +++ b/Source/extensions/bluetooth/audio/SDPSocket.cpp @@ -0,0 +1,232 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" +#include "SDPSocket.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace SDP { + + void Payload::PushDescriptor(const elementtype type, const uint32_t size) + { + ASSERT(_buffer != nullptr); + ASSERT(Free() >= 1); + + uint8_t* buffer = &_buffer[_writerOffset]; + uint32_t offset = 0; + + buffer[offset++] = (type | SIZE_8); + + switch (type) { + case NIL: + ASSERT(size == 0); + // Exception: even if size descriptor says BYTE, for NIL type there's no data following. + break; + case BOOL: + ASSERT(size == 1); + break; + case INT: + case UINT: + if (size == 1) { + // already set + } else if (size == 2) { + buffer[0] |= SIZE_16; + } else if (size == 4) { + buffer[0] |= SIZE_32; + } else if (size == 8) { + buffer[0] |= SIZE_64; + } else { + ASSERT(false && "Invalid INT size"); + } + break; + case UUID: + if (size == 2) { + buffer[0] |= SIZE_16; + } else if (size == 4) { + buffer[0] |= SIZE_32; + } else if (size == 16) { + buffer[0] |= SIZE_128; + } else { + ASSERT(false && "Invalid UUID size"); + } + break; + case TEXT: + case SEQ: + case ALT: + case URL: + if (size <= 0xFF) { + ASSERT(Free() >= 1); + buffer[0] |= SIZE_U8_FOLLOWS; + } else if (size <= 0xFFFF) { + ASSERT(Free() >= 2); + buffer[0] |= SIZE_U16_FOLLOWS; + buffer[offset++] = (size >> 8); + } else { + ASSERT(Free() >= 4); + buffer[0] |= SIZE_U32_FOLLOWS; + buffer[offset++] = (size >> 24); + buffer[offset++] = (size >> 16); + buffer[offset++] = (size >> 8); + } + buffer[offset++] = size; + break; + } + + _writerOffset += offset; + } + + uint8_t Payload::ReadDescriptor(elementtype& type, uint32_t& size) const + { + uint8_t offset = 0; + uint8_t t = _buffer[_readerOffset + offset++]; + + switch (t & 7) { + case SIZE_8: + size = 1; + break; + case SIZE_16: + size = 2; + break; + case SIZE_32: + size = 4; + break; + case SIZE_64: + size = 8; + break; + case SIZE_128: + size = 16; + break; + case SIZE_U8_FOLLOWS: + size = _buffer[_readerOffset + offset++]; + break; + case SIZE_U16_FOLLOWS: + size = (_buffer[_readerOffset + offset++] << 8); + size |= _buffer[_readerOffset + offset++]; + break; + case SIZE_U32_FOLLOWS: + size = (_buffer[_readerOffset + offset++] << 24); + size |= (_buffer[_readerOffset + offset++] << 16); + size |= (_buffer[_readerOffset + offset++] << 8); + size |= _buffer[_readerOffset + offset++]; + break; + default: + TRACE_L1("SDP: Unexpected descriptor size [0x%01x]", (t & 7)); + size = 0; + break; + } + + type = static_cast(t & 0xF8); + if (type == NIL) { + size = 0; + } + + return (offset); + } + + uint16_t PDU::Serialize(uint8_t stream[], const uint16_t length) const + { + ASSERT(stream != nullptr); + + // The request must always fit into MTU! + ASSERT((HeaderSize + _payload.Length()) <= length); + + DataRecordBE msg(stream, length, 0); + + if (_errorCode != InProgress) { + msg.Push(_type); + msg.Push(_transactionId); + + if (Type() == PDU::ErrorResponse) { + msg.Push(2); + msg.Push(_errorCode); + } else { + msg.Push(_payload.Length()); + msg.Push(_payload); + } + + _errorCode = InProgress; + } + + if (msg.Length() != 0) { + TRACE_L1("SDP: sent %s; result: %d", AsString().c_str(), _errorCode); + } + + return (msg.Length()); + } + + uint16_t PDU::Deserialize(const uint8_t stream[], const uint16_t length) + { + ASSERT(stream != nullptr); + + DataRecordBE msg(stream, length); + bool truncated = false; + + _errorCode = Success; + + if (msg.Available() >= sizeof(_type)) { + msg.Pop(_type); + } else { + truncated = true; + } + + if (truncated == false) { + if (msg.Available() >= (sizeof(_transactionId) + sizeof(uint16_t))) { + msg.Pop(_transactionId); + + uint16_t payloadLength{}; + msg.Pop(payloadLength); + + if (payloadLength > 0) { + if (msg.Available() >= payloadLength) { + if (Type() == PDU::ErrorResponse) { + if (payloadLength >= 2) { + msg.Pop(_errorCode); + } else { + truncated = true; + } + } else { + msg.Pop(_payload, payloadLength); + } + } else { + truncated = true; + } + } + } else { + truncated = true; + } + } + + if (truncated == true) { + TRACE_L1("SDP: Message was truncated"); + _errorCode = DeserializationFailed; + } + + TRACE_L1("SDP: received %s; result: %d", AsString().c_str(), _errorCode); + + return (length); + } + +} // namespace SDP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/SDPSocket.h b/Source/extensions/bluetooth/audio/SDPSocket.h new file mode 100644 index 000000000..1b2c4a67f --- /dev/null +++ b/Source/extensions/bluetooth/audio/SDPSocket.h @@ -0,0 +1,913 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "DataRecord.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace SDP { + + static constexpr uint8_t PSM = 1; + static constexpr uint16_t SocketBufferSize = 2048; + + struct EXTERNAL use_descriptor_t { explicit use_descriptor_t() = default; }; + constexpr use_descriptor_t use_descriptor = use_descriptor_t{}; // tag for selecting a proper overload + + struct EXTERNAL use_length_t { explicit use_length_t() = default; }; + constexpr use_length_t use_length = use_length_t{}; + + class EXTERNAL Payload : public DataRecordBE { + public: + enum elementtype : uint8_t { + NIL = 0x00, + UINT = 0x08, + INT = 0x10, + UUID = 0x18, + TEXT = 0x20, + BOOL = 0x28, + SEQ = 0x30, + ALT = 0x38, + URL = 0x40, + }; + + enum sizetype { + SIZE_8 = 0, + SIZE_16 = 1, + SIZE_32 = 2, + SIZE_64 = 3, + SIZE_128 = 4, + SIZE_U8_FOLLOWS = 5, + SIZE_U16_FOLLOWS = 6, + SIZE_U32_FOLLOWS = 7 + }; + + public: + using Builder = std::function; + using Inspector = std::function; + + enum class Continuation : uint8_t { + ABSENT = 0, + FOLLOWS + }; + + public: + using DataRecordBE::DataRecordBE; + using DataRecordBE::Pop; + using DataRecordBE::Push; + using DataRecordBE::PopAssign; + + ~Payload() = default; + + public: + void Push(use_descriptor_t) + { + PushDescriptor(NIL); + } + void Push(const Bluetooth::UUID& value) + { + ASSERT(Free() >= value.Length()); + uint8_t size = value.Length(); + while (size-- > 0) { + _buffer[_writerOffset++] = value.Data()[size]; // reverse! + } + } + void Push(use_descriptor_t, const Bluetooth::UUID& value) + { + PushDescriptor(UUID, value.Length()); + Push(value); + } + void Push(use_descriptor_t, const string& value, bool url = false) + { + PushDescriptor((url? URL : TEXT), value.length()); + Push(value); + } + void Push(use_descriptor_t, const bool value) + { + PushDescriptor(BOOL, 1); + Push(value); + } + template::value, int>::type = 0> + void Push(use_descriptor_t, const TYPE value) + { + PushDescriptor((std::numeric_limits::is_signed? INT : UINT), sizeof(TYPE)); + Push(value); + } + template::value, int>::type = 0> + void Push(use_descriptor_t, const TYPE value) + { + PushDescriptor(UINT, sizeof(TYPE)); + Push(value); + } + void Push(use_descriptor_t, const Payload& sequence, const bool alternative = false) + { + PushDescriptor((alternative? ALT : SEQ), sequence.Length()); + if (sequence.Length() != 0) { + Push(sequence); + } + } + void Push(use_length_t, const Payload& sequence, const bool = false) + { + Push(sequence.Length()); + if (sequence.Length() != 0) { + Push(sequence); + } + } + void Push(use_descriptor_t, const Buffer& sequence, const bool alternative = false) + { + PushDescriptor((alternative? ALT : SEQ), sequence.size()); + if (sequence.size() != 0) { + Push(sequence); + } + } + void Push(use_length_t, const Buffer& sequence, const bool = false) + { + ASSERT(sequence.size() < 0x10000); + Push(sequence.size()); + if (sequence.size() != 0) { + Push(sequence); + } + } + void Push(use_descriptor_t, const uint8_t sequence[], const uint16_t length, const bool alternative = false) + { + if (length != 0) { + PushDescriptor((alternative? ALT : SEQ), length); + Push(sequence, length); + } + } + void Push(use_length_t, const uint8_t sequence[], const uint16_t length) + { + Push(length); + if (length != 0) { + Push(sequence, length); + } + } + void Push(const Builder& Build, const uint16_t scratchPadSize = 2048) + { + uint8_t* scratchPad = static_cast(ALLOCA(scratchPadSize)); + Payload sequence(scratchPad, scratchPadSize, 0); + Build(sequence); + Push(sequence); + } + template + void Push(TAG tag, const Builder& Build, const bool alternative = false, const uint16_t scratchPadSize = 2048) + { + uint8_t* scratchPad = static_cast(ALLOCA(scratchPadSize)); + Payload sequence(scratchPad, scratchPadSize, 0); + Build(sequence); + Push(tag, sequence, alternative); + } + template + void Push(const std::list& list, const uint16_t scratchPadSize = 2048) + { + if (list.size() != 0) { + ASSERT(Free() >= (list.size() * sizeof(TYPE))); + Push([&](Payload& sequence){ + for (const auto& item : list) { + sequence.Push(item); + } + }, scratchPadSize); + } + } + template + void Push(use_descriptor_t, const std::list& list, const bool alternative = false, const uint16_t scratchPadSize = 2048) + { + if (list.size() != 0) { + Push(use_descriptor, [&](Payload& sequence){ + for (const auto& item : list) { + sequence.Push(use_descriptor, item); + } + }, alternative, scratchPadSize); + } + } + + public: + void Pop(use_descriptor_t, string& value) const + { + elementtype type; + uint32_t size = 0; + _readerOffset += ReadDescriptor(type, size); + if ((type == TEXT) || (type == URL)) { + Pop(value, size); + } else { + TRACE_L1("SDP: Unexpected descriptor in payload [0x%02x], expected TEXT or URL", type); + _readerOffset += size; + } + } + template::value, int>::type = 0> + void Pop(use_descriptor_t, TYPE& value) + { + elementtype type; + uint32_t size = 0; + _readerOffset += ReadDescriptor(type, size); + if (type == UINT) { + typename std::underlying_type::type temp; + if (size != sizeof(temp)) { + TRACE_L1("SDP: Warning: enum underlying type size does not match!"); + } + Pop(temp); + value = static_cast(temp); + } else { + TRACE_L1("SDP: Unexpected descriptor in payload [0x%02x], expected a UINT for enum", type); + _readerOffset += size; + } + } + template::value, int>::type = 0> + void Pop(use_descriptor_t, TYPE& value, uint32_t* outSize = nullptr) const + { + elementtype type; + uint32_t size = 0; + _readerOffset += ReadDescriptor(type, size); + if (type == (std::numeric_limits::is_signed? INT : UINT)) { + if (size > sizeof(TYPE)) { + TRACE_L1("SDP: Warning: integer value possibly truncated!"); + } + if (size == 1) { + uint8_t val{}; + DataRecord::PopIntegerValue(val); + value = val; + } else if (size == 2) { + uint16_t val{}; + PopIntegerValue(val); + value = val; + } else if (size == 4) { + uint32_t val{}; + PopIntegerValue(val); + value = val; + } else { + TRACE_L1("SDP: Unexpected integer size"); + _readerOffset += size; + } + if (outSize != nullptr) { + (*outSize) = size; + } + } else { + TRACE_L1("SDP: Unexpected descriptor in payload [0x%02x], expected %s", type, (std::numeric_limits::is_signed? "an INT" : "a UINT")); + _readerOffset += size; + } + } + template + void Pop(std::vector& vect, const uint16_t count) const + { + if (Available() >= (count * sizeof(TYPE))) { + vect.reserve(count); + + uint16_t i = count; + while (i-- > 0) { + TYPE item; + Pop(item); + vect.push_back(item); + } + } else { + TRACE_L1("SDP: Truncated payload while reading a vector"); + _readerOffset = _writerOffset; + } + } + template + void Pop(std::list& list, const uint16_t count) const + { + if (Available() >= (count * sizeof(TYPE))) { + uint16_t i = count; + while (i-- > 0) { + TYPE item; + Pop(item); + list.push_back(item); + } + } else { + TRACE_L1("SDP: Truncated payload while reading a list"); + _readerOffset = _writerOffset; + } + } + template + void Pop(use_descriptor_t, std::list& list, const uint16_t count) const + { + if (Available() >= (count * sizeof(TYPE))) { + uint16_t i = count; + while (i-- > 0) { + TYPE item; + Pop(use_descriptor, item); + list.push_back(item); + } +} else { + TRACE_L1("SDP: Truncated payload while reading a list"); + _readerOffset = _writerOffset; + } + } + void Pop(use_descriptor_t, Bluetooth::UUID& uuid) const + { + uint32_t size = 0; + elementtype type; + _readerOffset += ReadDescriptor(type, size); + if (type == UUID) { + if (Available() >= size) { + if (size == 2) { + uuid = Bluetooth::UUID((_buffer[_readerOffset] << 8) | _buffer[_readerOffset + 1]); + } else if (size == 4) { + uuid = Bluetooth::UUID((_buffer[_readerOffset] << 24) | (_buffer[_readerOffset + 1] << 16) + | (_buffer[_readerOffset + 2] << 8) | _buffer[_readerOffset + 3]); + } else { + uint8_t* buffer = static_cast(ALLOCA(size)); + uint8_t i = size; + while (i-- > 0) { + buffer[i] = _buffer[_readerOffset++]; + } + uuid = Bluetooth::UUID(buffer); + } + _readerOffset += size; + } else { + TRACE_L1("SDP: Truncated payload while reading UUID"); + _readerOffset = _writerOffset; + } + } else { + TRACE_L1("SDP: Unexpected descriptor in payload [0x%02x], expected a UUID", type); + _readerOffset += size; + } + } + void Pop(use_descriptor_t, const Inspector& inspector) const + { + uint32_t size = 0; + elementtype type; + _readerOffset += ReadDescriptor(type, size); + if (type == SEQ) { + if (Available() >= size) { + Payload sequence(&_buffer[_readerOffset], size, size); + inspector(sequence); + _readerOffset += size; + } else { + TRACE_L1("SDP: Truncated payload while reading a sequence"); + _readerOffset = _writerOffset; + } + } else { + TRACE_L1("SDP: Unexpected descriptor in payload [0x%02x], expected a SEQ", type); + _readerOffset += size; + } + } + void Pop(use_descriptor_t, Buffer& element) const + { + elementtype elemType; + uint32_t elemSize; + uint8_t descriptorSize = ReadDescriptor(elemType, elemSize); + // Don't assume any type of data here. + if (Available() >= (descriptorSize + elemSize)) { + element.assign(&_buffer[_readerOffset], (descriptorSize + elemSize)); + _readerOffset += (descriptorSize + elemSize); + } else { + TRACE_L1("SDP: Truncated payload while reading a sequence"); + _readerOffset = _writerOffset; + } + } + void Pop(use_descriptor_t, Buffer& element, const uint16_t size) const + { + // Don't assume any type of data here. + if (Available() >= size) { + element.assign(&_buffer[_readerOffset], size); + _readerOffset += size; + } else { + TRACE_L1("SDP: Truncated payload while reading a buffer"); + _readerOffset = _writerOffset; + } + } + void Pop(use_length_t, const Inspector& inspector) const + { + uint16_t size{}; + Pop(size); + if (Available() >= size) { + Payload sequence(&_buffer[_readerOffset], size); + inspector(sequence); + _readerOffset += size; + } else { + TRACE_L1("SDP: Truncated payload while reading a buffer"); + _readerOffset = _writerOffset; + } + } + void Pop(use_length_t, Buffer& element) const + { + uint16_t size{}; + Pop(size); + if (Available() >= size) { + element.assign(&_buffer[_readerOffset], size); + _readerOffset += size; + } else { + TRACE_L1("SDP: Truncated payload while reading a buffer"); + _readerOffset = _writerOffset; + } + } + + public: + void PopAssign(use_descriptor_t, Payload& element) const + { + elementtype elemType; + uint32_t elemSize; + uint8_t descriptorSize = ReadDescriptor(elemType, elemSize); + if (Available() >= (descriptorSize + elemSize)) { + element.Assign(&_buffer[_readerOffset], (descriptorSize + elemSize)); + _readerOffset += (descriptorSize + elemSize); + } else { + TRACE_L1("SDP: Truncated payload"); + _readerOffset = _writerOffset; + } + } + + private: + void PushDescriptor(const elementtype type, const uint32_t size = 0); + uint8_t ReadDescriptor(elementtype& type, uint32_t& size) const; + }; // class Payload + + class EXTERNAL PDU { + public: + static constexpr uint8_t HeaderSize = 5; + static constexpr uint8_t MaxContinuationSize = 16; + + public: + enum pduid : uint8_t { + Invalid = 0, + ErrorResponse = 1, + ServiceSearchRequest = 2, + ServiceSearchResponse = 3, + ServiceAttributeRequest = 4, + ServiceAttributeResponse = 5, + ServiceSearchAttributeRequest = 6, + ServiceSearchAttributeResponse = 7, + }; + + enum errorid : uint16_t { + Success = 0, + UnsupportedSdpVersion = 1, + InvalidServiceRecordHandle = 2, + InvalidRequestSyntax = 3, + InvalidPduSize = 4, + InvalidContinuationState = 5, + InsufficientResources = 6, + Reserved = 255, + InProgress, + SerializationFailed, + DeserializationFailed, + }; + + public: + PDU(const PDU&) = delete; + PDU& operator=(const PDU&) = delete; + ~PDU() = default; + + explicit PDU(const uint16_t bufferSize = SocketBufferSize) + : _buffer(new uint8_t[bufferSize]) + , _payload(_buffer.get(), bufferSize, 0) + , _type(Invalid) + , _transactionId(~0) + , _errorCode(Reserved) + { + ASSERT(bufferSize > (HeaderSize + (1 /* continuation length */) + MaxContinuationSize)); + ASSERT(_buffer.get() != nullptr); + } + + public: + string AsString() const + { +#ifdef __DEBUG__ + const char* labels[] = { "Invalid", "ErrorResponse", "ServiceSearchRequest", "ServiceSearchResponse", "ServiceAttributeRequest", + "ServiceAttributeResponse", "ServiceSearchAttributeRequest", "ServiceSearchAttributeResponse" }; + + ASSERT(Type() <= ServiceSearchAttributeResponse); + return (Core::Format("PDU #%d '%s'", _transactionId, labels[_type])); +#else + return (Core::Format("PDU #%d type %d", _transactionId, _type)); +#endif + } + + public: + bool IsValid() const { + return ((Type() != Invalid) && (TransactionId() != static_cast(~0))); + } + pduid Type() const { + return (_type); + } + uint16_t TransactionId() const { + return (_transactionId); + } + errorid Error() const { + return (_errorCode); + } + uint16_t Capacity() const { + return (_payload.Capacity()); + } + + public: + void InspectPayload(const Payload::Inspector& inspectorCb) const + { + ASSERT(inspectorCb != nullptr); + + _payload.Rewind(); + inspectorCb(_payload); + } + + public: + void Reload() const + { + _payload.Rewind(); + } + void Clear() + { + _payload.Clear(); + _type = Invalid; + _transactionId = -1; + _errorCode = Reserved; + } + void Set(const uint16_t transactionId, const pduid type, const Payload::Builder& buildCb) + { + _errorCode = SerializationFailed; + _transactionId = transactionId; + _type = type; + _payload.Clear(); + + if (buildCb != nullptr) { + buildCb(_payload); + } + } + + public: + uint16_t Serialize(uint8_t stream[], const uint16_t length) const; + uint16_t Deserialize(const uint8_t stream[], const uint16_t length); + + private: + std::unique_ptr _buffer; + Payload _payload; + pduid _type; + uint16_t _transactionId; + mutable errorid _errorCode; + }; // class PDU + + class EXTERNAL ClientSocket : public Core::SynchronousChannelType { + public: + static constexpr uint32_t CommunicationTimeout = 2000; /* 2 seconds. */ + + class EXTERNAL Command : public Core::IOutbound, public Core::IInbound { + public: + class EXTERNAL Request : public PDU { + public: + Request(const Request&) = delete; + Request& operator=(const Request&) = delete; + ~Request() = default; + + Request() + : PDU() + , _counter(~0) + { + } + + public: + using PDU::Set; + + void Set(const PDU::pduid type, const Payload::Builder& buildCb = nullptr) + { + Set(Counter(), type, buildCb); + } + + private: + uint16_t Counter() const { + return (++_counter); + } + + private: + mutable uint16_t _counter; + }; // class Request + + public: + class EXTERNAL Response : public PDU { + public: + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + ~Response() = default; + + Response() + : PDU() + { + } + }; // class Response + + public: + Command(const Command&) = delete; + Command& operator=(const Command&) = delete; + + Command(ClientSocket& socket) + : _request() + , _response() + , _socket(socket) + { + } + ~Command() = default; + + public: + template + void Set(Args&&... args) + { + _response.Clear(); + _request.Set(std::forward(args)...); + } + + public: + Response& Result() { + return (_response); + } + const Response& Result() const + { + return (_response); + } + const Request& Call() const + { + return (_request); + } + bool IsValid() const + { + return (_request.IsValid()); + } + + private: + void Reload() const override + { + _request.Reload(); + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + ASSERT(stream != nullptr); + + const uint16_t result = _request.Serialize(stream, std::min(_socket.OutputMTU(), length)); + + if (result != 0) { + CMD_DUMP("SDP client send", stream, result); + } + + return (result); + } + uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + ASSERT(stream != nullptr); + + CMD_DUMP("SDP client received", stream, length); + + return (_response.Deserialize(stream, length)); + } + Core::IInbound::state IsCompleted() const override + { + return (Core::IInbound::COMPLETED); + } + + private: + Request _request; + Response _response; + ClientSocket& _socket; + }; // class Command + + public: + ClientSocket(const ClientSocket&) = delete; + ClientSocket& operator=(const ClientSocket&) = delete; + ~ClientSocket() = default; + + ClientSocket(const Core::NodeId& localNode, const Core::NodeId& remoteNode) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, + localNode, remoteNode, SocketBufferSize, SocketBufferSize) + , _adminLock() + , _omtu(0) + { + } + ClientSocket(const SOCKET& connector, const Core::NodeId& remoteNode) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, + connector, remoteNode, SocketBufferSize, SocketBufferSize) + , _adminLock() + , _imtu(0) + , _omtu(0) + { + } + + public: + uint16_t InputMTU() const { + return (_omtu); + } + uint16_t OutputMTU() const { + return (_omtu); + } + + private: + virtual void Operational(const bool upAndRunning) = 0; + + void StateChange() override + { + Core::SynchronousChannelType::StateChange(); + + if (IsOpen() == true) { + struct l2cap_options options{}; + socklen_t len = sizeof(options); + + ::getsockopt(Handle(), SOL_L2CAP, L2CAP_OPTIONS, &options, &len); + + ASSERT(options.omtu <= SendBufferSize()); + ASSERT(options.imtu <= ReceiveBufferSize()); + + TRACE(Trace::Information, (_T("SDP channel input MTU: %d, output MTU: %d"), options.imtu, options.omtu)); + + _omtu = options.omtu; + _imtu = options.imtu; + + Operational(true); + + } else { + Operational(false); + + _omtu = 0; + _imtu = 0; + } + } + + uint16_t Deserialize(const uint8_t stream[] VARIABLE_IS_NOT_USED, const uint16_t length) override + { + if (length != 0) { + TRACE_L1("SDP: Unexpected data for deserialization [%d bytes]", length); + CMD_DUMP("SDP client received unexpected", stream, length); + } + + return (length); + } + + private: + Core::CriticalSection _adminLock; + uint16_t _imtu; + uint16_t _omtu; + }; // class ClientSocket + + class EXTERNAL ServerSocket : public ClientSocket { + public: + class EXTERNAL ResponseHandler { + public: + ResponseHandler(const ResponseHandler&) = default; + ResponseHandler& operator=(const ResponseHandler&) = default; + ~ResponseHandler() = default; + + ResponseHandler(const std::function& acceptor, + const std::function& rejector) + : _acceptor(acceptor) + , _rejector(rejector) + { + } + + public: + void operator ()(const PDU::pduid newId, const Payload::Builder& buildCb = nullptr) const + { + _acceptor(newId, buildCb); + } + void operator ()(const PDU::errorid result) const + { + _rejector(result); + } + + private: + std::function _acceptor; + std::function _rejector; + }; // class ResponseHandler + + private: + class EXTERNAL Request : public PDU { + public: + Request(const Request&) = delete; + Request& operator=(const Request&) = delete; + ~Request() = default; + + Request() + : PDU() + { + } + }; // class Request + + class EXTERNAL Response : public PDU, public Core::IOutbound { + public: + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + Response(ServerSocket& socket) + : PDU() + , _socket(socket) + { + } + ~Response() = default; + + public: + using PDU::Set; + + void Set(const uint16_t transactionId, PDU::errorid code) + { + Set(transactionId, PDU::ErrorResponse, [code](Payload& payload) { + payload.Push(code); + }); + } + + public: + const ServerSocket& Socket() const { + return (_socket); + } + + public: + void Reload() const override + { + PDU::Reload(); + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + const uint16_t result = PDU::Serialize(stream, std::min(_socket.OutputMTU(), length)); + + if (result != 0) { + CMD_DUMP("SDP server sent", stream, result); + } + + return (result); + } + + private: + ServerSocket& _socket; + }; // class Response + + public: + ServerSocket(const ServerSocket&) = delete; + ServerSocket& operator=(const ServerSocket&) = delete; + + ServerSocket(const Core::NodeId& localNode, const Core::NodeId& remoteNode) + : ClientSocket(localNode, remoteNode) + , _request() + , _response(*this) + { + } + ServerSocket(const SOCKET& connector, const Core::NodeId& remoteNode) + : ClientSocket(connector, remoteNode) + , _request() + , _response(*this) + { + } + ~ServerSocket() = default; + + public: + uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override + { + uint16_t result = 0; + + CMD_DUMP("SDP server received", stream, length); + + result = _request.Deserialize(stream, length); + + if (result != 0) { + Received(_request, _response); + _request.Clear(); + } + + return (result); + } + + private: + void Received(const Request& request, Response& response) + { + if (request.IsValid() == true) { + OnPDU(*this, request, ResponseHandler( + [&](const PDU::pduid newId, const Payload::Builder& buildCb) { + TRACE_L1("SDP server: accepting %s", request.AsString().c_str()); + response.Set(request.TransactionId(), newId, buildCb); + }, + [&](const PDU::errorid result) { + TRACE_L1("SDP server: rejecting %s, reason: %d", request.AsString().c_str(), result); + response.Set(request.TransactionId(), result); + })); + } else { + TRACE_L1("SDP server: Invalid request!"); + response.Set(request.TransactionId(), PDU::InvalidRequestSyntax); + } + + Send(CommunicationTimeout, response, nullptr, nullptr); + } + + protected: + virtual void OnPDU(const ServerSocket& socket, const PDU& request, const ResponseHandler& handler) = 0; + + private: + Request _request; + Response _response; + }; + +} // namespace SDP + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/audio/bluetooth_audio.h b/Source/extensions/bluetooth/audio/bluetooth_audio.h new file mode 100644 index 000000000..7bdfe9fac --- /dev/null +++ b/Source/extensions/bluetooth/audio/bluetooth_audio.h @@ -0,0 +1,39 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#error "Please define a MODULE_NAME that describes the binary/library you are building." +#endif + +#include +#include "SDPSocket.h" +#include "SDPProfile.h" +#include "AVDTPSocket.h" +#include "AVDTPProfile.h" +#include "RTPSocket.h" +#include "DataRecord.h" + +#include "IAudioCodec.h" +#include "IAudioContentProtection.h" + +#ifdef __WINDOWS__ +#pragma comment(lib, "bluetoothaudio.lib") +#endif \ No newline at end of file diff --git a/Source/extensions/bluetooth/audio/cmake/FindSBC.cmake b/Source/extensions/bluetooth/audio/cmake/FindSBC.cmake new file mode 100644 index 000000000..ef432832f --- /dev/null +++ b/Source/extensions/bluetooth/audio/cmake/FindSBC.cmake @@ -0,0 +1,34 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2021 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# - Try to find sbc +# Once done this will define +# SBC_FOUND - System has libsbc +# SBC_INCLUDE_DIRS - The libsbc include directories +# SBC_LIBRARIES - The libraries needed to use libsbc + +find_package(PkgConfig) +pkg_check_modules(SBC REQUIRED sbc IMPORTED_TARGET) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SBC DEFAULT_MSG SBC_LIBRARIES SBC_INCLUDE_DIRS) + +mark_as_advanced(SBC_FOUND SBC_LIBRARIES SBC_INCLUDE_DIRS) + +if(SBC_FOUND) + add_library(SBC::SBC ALIAS PkgConfig::SBC) +endif() \ No newline at end of file diff --git a/Source/extensions/bluetooth/audio/codecs/SBC.cpp b/Source/extensions/bluetooth/audio/codecs/SBC.cpp new file mode 100644 index 000000000..a40b17e30 --- /dev/null +++ b/Source/extensions/bluetooth/audio/codecs/SBC.cpp @@ -0,0 +1,654 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Module.h" + +#include "SBC.h" + +#include + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(Bluetooth::A2DP::SBC::preset) + { Bluetooth::A2DP::SBC::COMPATIBLE, _TXT("Compatible") }, + { Bluetooth::A2DP::SBC::LQ, _TXT("LQ") }, + { Bluetooth::A2DP::SBC::MQ, _TXT("MQ") }, + { Bluetooth::A2DP::SBC::HQ, _TXT("HQ") }, + { Bluetooth::A2DP::SBC::XQ, _TXT("XQ") }, +ENUM_CONVERSION_END(Bluetooth::A2DP::SBC::preset) + +ENUM_CONVERSION_BEGIN(Bluetooth::A2DP::SBC::Config::channelmode) + { Bluetooth::A2DP::SBC::Config::MONO, _TXT("Mono") }, + { Bluetooth::A2DP::SBC::Config::STEREO, _TXT("Stereo") }, + { Bluetooth::A2DP::SBC::Config::JOINT_STEREO, _TXT("JointSstereo") }, + { Bluetooth::A2DP::SBC::Config::DUAL_CHANNEL, _TXT("DualChannel") }, +ENUM_CONVERSION_END(Bluetooth::A2DP::SBC::Config::channelmode) + +namespace Bluetooth { + +namespace A2DP { + + /* virtual */ uint32_t SBC::Configure(const StreamFormat& format, const string& settings) + { + uint32_t result = Core::ERROR_NONE; + + Core::JSON::String Data; + Core::JSON::Container container; + container.Add("LC-SBC", &Data); + container.FromString(settings); + + Config config; + config.FromString(Data.Value()); + + _lock.Lock(); + + preset preferredPreset = HQ; + Format::samplingfrequency frequency = Format::SF_INVALID; + Format::channelmode channelMode = Format::CM_INVALID; + uint8_t maxBitpool = _supported.MinBitpool(); + + if (config.Preset.IsSet() == true) { + // Preset requested in config, this will now be the target quality. + if (config.Preset.Value() != COMPATIBLE) { + preferredPreset = config.Preset.Value(); + } + else { + preferredPreset = LQ; + } + } + + switch (format.SampleRate) { + case 16000: + frequency = Format::SF_16000_HZ; + break; + case 32000: + frequency = Format::SF_32000_HZ; + break; + case 44100: + frequency = Format::SF_44100_HZ; + break; + case 48000: + frequency = Format::SF_48000_HZ; + break; + default: + break; + } + + frequency = static_cast(frequency & _supported.SamplingFrequency()); + + switch (format.Channels) { + case 1: + channelMode = Format::CM_MONO; + break; + case 2: + if (config.ChannelMode.IsSet() == true) { + + switch (config.ChannelMode) { + case Config::STEREO: + channelMode = Format::CM_STEREO; + break; + case Config::DUAL_CHANNEL: + channelMode = Format::CM_DUAL_CHANNEL; + break; + default: + break; + } + } + + if (channelMode == Format::CM_INVALID) { + // Joint-Stereo compresses better than regular stereo when the signal is concentrated + // in the middle of the stereo image, what is quite typical. + channelMode = Format::CM_JOINT_STEREO; + } + + break; + } + + channelMode = static_cast(channelMode & _supported.ChannelMode()); + + if ((channelMode != Format::CM_INVALID) && (frequency != Format::SF_INVALID) && (format.Resolution == 16)) { + bool stereo = (channelMode != Format::CM_MONO); + + // Select bitpool and channel mode based on preferred format... + // (Note that bitpools for sample rates of 16 and 32 kHz are not specified, hence will receive same values as 44,1 kHz.) + if (preferredPreset == XQ) { + if (_supported.MaxBitpool() >= 38) { + maxBitpool = 38; + + if (stereo == true) { + if (_supported.MaxBitpool() >= 76) { + maxBitpool = 76; + } + else { + // Max supported bitpool is too low for XQ joint-stereo, so try 38 on dual channel instead + // - that should give similar quality/bitrate. + channelMode = Format::CM_DUAL_CHANNEL; + } + } + } + else { + // XQ not supported, drop to the next best one... + preferredPreset = HQ; + } + } + + if (preferredPreset == HQ) { + if (frequency == Format::SF_48000_HZ) { + maxBitpool = (stereo? 51 : 29); + } + else { + maxBitpool = (stereo? 53 : 31); + } + + if (maxBitpool > _supported.MaxBitpool()) { + preferredPreset = MQ; + } + } + + if (preferredPreset == MQ) { + if (frequency == Format::SF_48000_HZ) { + maxBitpool = (stereo? 33 : 18); + } + else { + maxBitpool = (stereo? 35 : 19); + } + + if (maxBitpool > _supported.MaxBitpool()) { + preferredPreset = LQ; + } + } + + if (preferredPreset == LQ) { + maxBitpool = (stereo? 29 : 15); + + if (maxBitpool > _supported.MaxBitpool()) { + preferredPreset = COMPATIBLE; + } + } + + if (preferredPreset == COMPATIBLE) { + // Use whatever is the maximum supported bitpool. + maxBitpool = _supported.MaxBitpool(); + } + + ASSERT(maxBitpool <= MAX_BITPOOL); + + _actuals.SamplingFrequency(frequency); + _actuals.ChannelMode(channelMode); + _actuals.MinBitpool(_supported.MinBitpool()); + _actuals.MaxBitpool(maxBitpool); + _preferredBitpool = maxBitpool; + _preset = preferredPreset; + +#ifdef __DEBUG__ + DumpConfiguration(); +#endif + + Bitpool(maxBitpool); + } + else { + result = Core::ERROR_NOT_SUPPORTED; + TRACE(Trace::Error, (_T("Unsuppored SBC paramters requested"))); + } + + _lock.Unlock(); + + return (result); + } + + /* virtual */ uint32_t SBC::Configure(const uint8_t stream[], const uint16_t length) + { + uint32_t result = Core::ERROR_NONE; + + _lock.Lock(); + + _actuals.Deserialize(stream, length); + + _preset = COMPATIBLE; + + _preferredBitpool = _actuals.MaxBitpool(); + + Bitpool(_actuals.MaxBitpool()); + + _lock.Unlock(); + + return (result); + } + + /* virtual */ void SBC::Configuration(StreamFormat& format, string& settings) const + { + Config config; + + _lock.Lock(); + + format.FrameRate = 0; + + format.Resolution = 16; // Always 16-bit samples + + switch (_actuals.SamplingFrequency()) { + case Format::SF_48000_HZ: + format.SampleRate = 48000; + break; + case Format::SF_44100_HZ: + format.SampleRate = 44100; + break; + case Format::SF_32000_HZ: + format.SampleRate = 32000; + break; + case Format::SF_16000_HZ: + format.SampleRate = 16000; + break; + default: + ASSERT(false && "Invalid sampling frequency configured"); + break; + } + + switch (_actuals.ChannelMode()) { + case Format::CM_MONO: + config.ChannelMode = Config::MONO; + format.Channels = 1; + break; + case Format::CM_DUAL_CHANNEL: + config.ChannelMode = Config::DUAL_CHANNEL; + format.Channels = 2; + break; + case Format::CM_STEREO: + config.ChannelMode = Config::STEREO; + format.Channels = 2; + break; + case Format::CM_JOINT_STEREO: + config.ChannelMode = Config::JOINT_STEREO; + format.Channels = 2; + break; + default: + ASSERT(false && "Invalid channel mode configured"); + break; + } + + config.Preset = _preset; + config.Bitpool = _bitpool; + + _lock.Unlock(); + + config.ToString(settings); + } + + /* virtual */ uint16_t SBC::Serialize(const bool capabilities, uint8_t stream[], const uint16_t length) const + { + _lock.Lock(); + + const uint16_t result = (capabilities? _supported.Serialize(stream, length) : _actuals.Serialize(stream, length)); + + _lock.Unlock(); + + return (result); + } + + /* virtual */ uint16_t SBC::Encode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outSize, uint8_t outBuffer[]) const + { + ASSERT(_rawFrameSize != 0); + ASSERT(_encodedFrameSize != 0); + + ASSERT(inBuffer != nullptr); + ASSERT(outBuffer != nullptr); + + uint16_t consumed = 0; + uint16_t produced = sizeof(SBCHeader); + uint16_t count = 0; + + _lock.Lock(); + + if ((_rawFrameSize != 0) && (_encodedFrameSize != 0)) { + + const uint8_t MAX_FRAMES = 15; // only a four bit number holds the number of frames in a packet + + uint16_t frames = (inBufferSize / _rawFrameSize); + uint16_t available = (outSize - produced); + + ASSERT(outSize >= sizeof(SBCHeader)); + + if (frames > MAX_FRAMES) { + frames = MAX_FRAMES; + } + + while ((frames-- > 0) + && (inBufferSize >= (consumed + _rawFrameSize)) + && (available >= _encodedFrameSize)) { + + ssize_t written = 0; + ssize_t read = ::sbc_encode(static_cast<::sbc_t*>(_sbcHandle), + (inBuffer + consumed), _rawFrameSize, + (outBuffer + produced), available, + &written); + + if (read < 0) { + TRACE_L1("Failed to encode an SBC frame!"); + break; + } + else { + consumed += read; + available -= written; + produced += written; + count++; + } + } + } + + _lock.Unlock(); + + if (count > 0) { + SBCHeader* header = reinterpret_cast(outBuffer); + header->frameCount = (count & 0xF); + outSize = produced; + } + else { + outSize = 0; + } + + return (consumed); + } + + /* virtual */ uint16_t SBC::Decode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outSize, uint8_t outBuffer[]) const + { + ASSERT(_rawFrameSize != 0); + ASSERT(_encodedFrameSize != 0); + + ASSERT(inBuffer != nullptr); + ASSERT(outBuffer != nullptr); + + uint16_t consumed = 0; + uint16_t produced = 0; + uint16_t available = outSize; + + _lock.Lock(); + + if ((_rawFrameSize != 0) && (_encodedFrameSize != 0)) { + + ASSERT(outSize >= sizeof(SBCHeader)); + const SBCHeader* header = reinterpret_cast(inBuffer); + + uint8_t frames = header->frameCount; + consumed = sizeof(SBCHeader); + + while ((frames != 0) + && (inBufferSize >= (consumed + _encodedFrameSize)) + && (available >= _rawFrameSize)) { + + size_t written = 0; + ssize_t read = ::sbc_decode(static_cast<::sbc_t*>(_sbcHandle), + (inBuffer + consumed), _encodedFrameSize, + (outBuffer + produced), available, + &written); + + + if (read < 0) { + TRACE_L1("Failed to decode an SBC frame!"); + break; + } + else { + available -= written; + produced += written; + consumed += read; + } + + frames--; + } + + ASSERT(frames == 0); + } + + _lock.Unlock(); + + outSize = produced; + + return (consumed); + } + + /* virtual */ uint32_t SBC::QOS(const int8_t policy) + { + uint32_t result = Core::ERROR_NONE; + + ASSERT(_preferredBitpool != 0); + + const uint8_t STEP = (_preferredBitpool / 10); + + _lock.Lock(); + + uint8_t newBitpool = _bitpool; + + if (policy == 0) { + // reset quality + newBitpool = _preferredBitpool; + } + else if (policy < 0) { + // decrease quality + if (newBitpool == _supported.MinBitpool()) { + result = Core::ERROR_UNAVAILABLE; + } + else if ((newBitpool - STEP) < _supported.MinBitpool()) { + newBitpool = _supported.MinBitpool(); + } + else { + newBitpool -= STEP; + } + } + else { + // increase quality + if (_bitpool == _preferredBitpool) { + newBitpool = Core::ERROR_UNAVAILABLE; + } + else if ((_bitpool + STEP) >= _preferredBitpool) { + newBitpool = _preferredBitpool; + } + else { + newBitpool += STEP; + } + } + + if (result == Core::ERROR_NONE) { + Bitpool(newBitpool); + } + + _lock.Unlock(); + + return (result); + } + + void SBC::Bitpool(uint8_t value) + { + ASSERT(value <= MAX_BITPOOL); + ASSERT(value >= MIN_BITPOOL); + + _bitpool = value; + + TRACE(Trace::Information, (_T("New bitpool value for SBC: %d"), _bitpool)); + + SBCConfigure(); + #ifdef __DEBUG__ + DumpBitrateConfiguration(); + #endif + } + + void SBC::SBCInitialize() + { + _lock.Lock(); + + ASSERT(_sbcHandle == nullptr); + + _sbcHandle = static_cast(new ::sbc_t); + ASSERT(_sbcHandle != nullptr); + + ::sbc_init(static_cast<::sbc_t*>(_sbcHandle), 0L); + + _lock.Unlock(); + } + + void SBC::SBCDeinitialize() + { + _lock.Lock(); + + if (_sbcHandle != nullptr) { + ::sbc_finish(static_cast<::sbc_t*>(_sbcHandle)); + + delete static_cast<::sbc_t*>(_sbcHandle); + + _sbcHandle = nullptr; + } + + _lock.Unlock(); + } + + void SBC::SBCConfigure() + { + _lock.Lock(); + + ASSERT(_sbcHandle != nullptr); + ::sbc_reinit(static_cast<::sbc_t*>(_sbcHandle), 0L); + + uint32_t rate; + uint32_t blocks; + uint32_t bands; + + ::sbc_t* sbc = static_cast<::sbc_t*>(_sbcHandle); + + sbc->bitpool = _bitpool; + sbc->allocation = (_actuals.AllocationMethod() == Format::AM_LOUDNESS? SBC_AM_LOUDNESS : SBC_AM_SNR); + + switch (_actuals.SubBands()) { + default: + case Format::SB_8: + sbc->subbands = SBC_SB_8; + bands = 8; + break; + case Format::SB_4: + sbc->subbands = SBC_SB_4; + bands = 4; + break; + } + + switch (_actuals.SamplingFrequency()) { + case Format::SF_48000_HZ: + sbc->frequency = SBC_FREQ_48000; + rate = 48000; + break; + default: + case Format::SF_44100_HZ: + sbc->frequency = SBC_FREQ_44100; + rate = 44100; + break; + case Format::SF_32000_HZ: + sbc->frequency = SBC_FREQ_32000; + rate = 32000; + break; + case Format::SF_16000_HZ: + sbc->frequency = SBC_FREQ_16000; + rate = 16000; + break; + } + + switch (_actuals.BlockLength()) { + default: + case Format::BL_16: + sbc->blocks = SBC_BLK_16; + blocks = 16; + break; + case Format::BL_12: + sbc->blocks = SBC_BLK_12; + blocks = 12; + break; + case Format::BL_8: + sbc->blocks = SBC_BLK_8; + blocks = 8; + break; + case Format::BL_4: + sbc->blocks = SBC_BLK_4; + blocks = 4; + break; + } + + switch (_actuals.ChannelMode()) { + default: + case Format::CM_JOINT_STEREO: + sbc->mode = SBC_MODE_JOINT_STEREO; + break; + case Format::CM_STEREO: + sbc->mode = SBC_MODE_STEREO; + break; + case Format::CM_DUAL_CHANNEL: + sbc->mode = SBC_MODE_DUAL_CHANNEL; + break; + case Format::CM_MONO: + sbc->mode = SBC_MODE_MONO; + break; + } + + _frameDuration = ::sbc_get_frame_duration(sbc); /* microseconds */ + _rawFrameSize = ::sbc_get_codesize(sbc); /* bytes */ + _encodedFrameSize = ::sbc_get_frame_length(sbc); /* bytes */ + + _bitRate = ((8L * _encodedFrameSize * rate) / (bands * blocks)); /* bits per second */ + _channels = (sbc->mode == SBC_MODE_MONO? 1 : 2); + _sampleRate = rate; + + _lock.Unlock(); + } + +#ifdef __DEBUG__ + void SBC::DumpConfiguration() const + { + #define ELEM(name, val, prop) (_T(" [ %d] " name " [ %d]"), !!(_supported.val() & Format::prop), !!(_actuals.val() & Format::prop)) + TRACE(Trace::Information, (_T("SBC configuration:"))); + TRACE(Trace::Information, ELEM("Sampling frequency - 16 kHz ", SamplingFrequency, SF_16000_HZ)); + TRACE(Trace::Information, ELEM("Sampling frequency - 32 kHz ", SamplingFrequency, SF_32000_HZ)); + TRACE(Trace::Information, ELEM("Sampling frequency - 44.1 kHz ", SamplingFrequency, SF_44100_HZ)); + TRACE(Trace::Information, ELEM("Sampling frequency - 48 kHz ", SamplingFrequency, SF_48000_HZ)); + TRACE(Trace::Information, ELEM("Channel mode - Mono ", ChannelMode, CM_MONO)); + TRACE(Trace::Information, ELEM("Channel mode - Stereo ", ChannelMode, CM_STEREO)); + TRACE(Trace::Information, ELEM("Channel mode - Dual Channel ", ChannelMode, CM_DUAL_CHANNEL)); + TRACE(Trace::Information, ELEM("Channel mode - Joint Stereo ", ChannelMode, CM_JOINT_STEREO)); + TRACE(Trace::Information, ELEM("Block length - 4 ", BlockLength, BL_4)); + TRACE(Trace::Information, ELEM("Block length - 8 ", BlockLength, BL_8)); + TRACE(Trace::Information, ELEM("Block length - 12 ", BlockLength, BL_12)); + TRACE(Trace::Information, ELEM("Block length - 16 ", BlockLength, BL_16)); + TRACE(Trace::Information, ELEM("Frequency sub-bands - 4 ", SubBands, SB_4)); + TRACE(Trace::Information, ELEM("Frequency sub-bands - 8 ", SubBands, SB_8)); + TRACE(Trace::Information, ELEM("Bit allocation method - SNR ", AllocationMethod, AM_SNR)); + TRACE(Trace::Information, ELEM("Bit allocation method - Loudness", AllocationMethod, AM_LOUDNESS)); + TRACE(Trace::Information, (_T(" [%3d] Minimal bitpool value [%3d]"), _supported.MinBitpool(), _actuals.MinBitpool())); + TRACE(Trace::Information, (_T(" [%3d] Maximal bitpool value [%3d]"), _supported.MaxBitpool(), _actuals.MaxBitpool())); + #undef ELEM + } + + void SBC::DumpBitrateConfiguration() const + { + Core::EnumerateType preset(_preset); + TRACE(Trace::Information, (_T("Quality preset: %s"), (preset.IsSet() == true? preset.Data() : "(custom)"))); + TRACE(Trace::Information, (_T("Bitpool value: %d"), _bitpool)); + TRACE(Trace::Information, (_T("Bitrate: %d bps"), _bitRate)); + TRACE(Trace::Information, (_T("Frame size: raw %d bytes, encoded %d bytes (%d us)"), _rawFrameSize, _encodedFrameSize, _frameDuration)); + } +#endif // __DEBUG__ + +} // namespace A2DP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/audio/codecs/SBC.h b/Source/extensions/bluetooth/audio/codecs/SBC.h new file mode 100644 index 000000000..e48036111 --- /dev/null +++ b/Source/extensions/bluetooth/audio/codecs/SBC.h @@ -0,0 +1,375 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2021 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../Module.h" +#include "../IAudioCodec.h" +#include "../DataRecord.h" + +namespace Thunder { + +namespace Bluetooth { + +namespace A2DP { + + class EXTERNAL SBC : public IAudioCodec { + public: + static constexpr uint8_t CODEC_TYPE = 0x00; // SBC + + static constexpr uint8_t MIN_BITPOOL = 2; + static constexpr uint8_t MAX_BITPOOL = 250; + + public: + enum preset { + COMPATIBLE, + LQ, + MQ, + HQ, + XQ + }; + + class Config : public Core::JSON::Container { + public: + enum channelmode { + MONO, + STEREO, + JOINT_STEREO, + DUAL_CHANNEL + }; + + public: + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config() + : Core::JSON::Container() + , Preset(COMPATIBLE) + , ChannelMode(JOINT_STEREO) + , Bitpool(MIN_BITPOOL) + { + Add(_T("preset"), &Preset); + Add(_T("channelmode"), &ChannelMode); + Add(_T("bitpool"), &Bitpool); + } + ~Config() = default; + + public: + Core::JSON::EnumType Preset; + Core::JSON::EnumType ChannelMode; + Core::JSON::DecUInt32 Bitpool; + }; // class Config + + class Format { + public: + enum samplingfrequency : uint8_t { + SF_INVALID = 0, + SF_48000_HZ = 1, // mandatory for sink + SF_44100_HZ = 2, // mandatory for sink + SF_32000_HZ = 4, + SF_16000_HZ = 8 + }; + + enum channelmode : uint8_t { + CM_INVALID = 0, + CM_JOINT_STEREO = 1, // all mandatory for sink + CM_STEREO = 2, + CM_DUAL_CHANNEL = 4, + CM_MONO = 8 + }; + + enum blocklength : uint8_t { + BL_INVALID = 0, + BL_16 = 1, // all mandatory for sink + BL_12 = 2, + BL_8 = 4, + BL_4 = 8, + }; + + enum subbands : uint8_t { + SB_INVALID = 0, + SB_8 = 1, // all mandatory for sink + SB_4 = 2, + }; + + enum allocationmethod : uint8_t { + AM_INVALID = 0, + AM_LOUDNESS = 1, // all mandatory for sink + AM_SNR = 2, + }; + + public: + Format() + : _samplingFrequency(SF_44100_HZ) + , _channelMode(CM_JOINT_STEREO) + , _blockLength(BL_16) + , _subBands(SB_8) + , _allocationMethod(AM_LOUDNESS) + , _minBitpool(MIN_BITPOOL) + , _maxBitpool(MIN_BITPOOL) // not an error + { + } + Format(const uint8_t stream[], const uint16_t length) + : _samplingFrequency(SF_44100_HZ) + , _channelMode(CM_JOINT_STEREO) + , _blockLength(BL_16) + , _subBands(SB_8) + , _allocationMethod(AM_LOUDNESS) + , _minBitpool(MIN_BITPOOL) + , _maxBitpool(MIN_BITPOOL) // not an error + { + Deserialize(stream, length); + } + Format(const uint16_t maxBitpool, const uint16_t minBitpool) + : _samplingFrequency(SF_16000_HZ | SF_32000_HZ | SF_44100_HZ | SF_48000_HZ) + , _channelMode(CM_MONO | CM_DUAL_CHANNEL | CM_STEREO | CM_JOINT_STEREO) + , _blockLength(BL_4 | BL_8 | BL_12 | BL_16) + , _subBands(SB_4 | SB_8) + , _allocationMethod(AM_LOUDNESS | AM_SNR) + , _minBitpool(minBitpool) + , _maxBitpool(maxBitpool) + { + } + ~Format() = default; + Format(const Format&) = default; + Format& operator=(const Format&) = default; + + public: + uint16_t Serialize(uint8_t stream[], const uint16_t length) const + { + ASSERT(length >= 6); + + uint8_t octet; + Bluetooth::DataRecord data(stream, length, 0); + + data.Push(IAudioCodec::MEDIA_TYPE); + data.Push(CODEC_TYPE); + + octet = ((static_cast(_samplingFrequency) << 4) | static_cast(_channelMode)); + data.Push(octet); + + octet = ((static_cast(_blockLength) << 4) | (static_cast(_subBands) << 2) | static_cast(_allocationMethod)); + data.Push(octet); + + data.Push(_minBitpool); + data.Push(_maxBitpool); + + return (data.Length()); + } + uint16_t Deserialize(const uint8_t stream[], const uint16_t length) + { + ASSERT(length >= 6); + + Bluetooth::DataRecord data(stream, length); + + uint8_t octet{}; + + data.Pop(octet); + ASSERT(octet == IAudioCodec::MEDIA_TYPE); + + data.Pop(octet); + ASSERT(octet == CODEC_TYPE); + + data.Pop(octet); + _samplingFrequency = (octet >> 4); + _channelMode = (octet & 0xF); + + data.Pop(octet); + _blockLength = (octet >> 4); + _subBands = ((octet >> 2) & 0x3); + _allocationMethod = (octet & 0x3); + + data.Pop(_minBitpool); + data.Pop(_maxBitpool); + + return (6); + } + + public: + uint8_t SamplingFrequency() const { + return (_samplingFrequency); + } + uint8_t ChannelMode() const { + return (_channelMode); + } + uint8_t BlockLength() const { + return (_blockLength); + } + uint8_t SubBands() const { + return (_subBands); + } + uint8_t AllocationMethod() const { + return (_allocationMethod); + } + uint8_t MinBitpool() const { + return (_minBitpool); + } + uint8_t MaxBitpool() const { + return (_maxBitpool); + } + + public: + void SamplingFrequency(const samplingfrequency sf) + { + _samplingFrequency = sf; + } + void ChannelMode(const channelmode cm) + { + _channelMode = cm; + } + void BlockLength(const blocklength bl) + { + _blockLength = bl; + } + void SubBands(const subbands sb) + { + _subBands = sb; + } + void AllocationMethod(const allocationmethod am) + { + _allocationMethod = am; + } + void MinBitpool(const uint8_t value) + { + _minBitpool = (value < MIN_BITPOOL? MIN_BITPOOL : value); + } + void MaxBitpool(const uint8_t value) + { + _maxBitpool = (value > MAX_BITPOOL? MAX_BITPOOL : value); + } + + private: + uint8_t _samplingFrequency; + uint8_t _channelMode; + uint8_t _blockLength; + uint8_t _subBands; + uint8_t _allocationMethod; + uint8_t _minBitpool; + uint8_t _maxBitpool; + }; // class Format + + public: + SBC(const uint8_t maxBitpool, const uint8_t minBitpool = 2) + : _lock() + , _supported(maxBitpool, minBitpool) + , _actuals() + , _preset(COMPATIBLE) + , _sbcHandle(nullptr) + , _preferredBitpool(0) + , _bitpool(0) + , _bitRate(0) + , _sampleRate(0) + , _channels(0) + , _rawFrameSize(0) + , _encodedFrameSize(0) + , _frameDuration(0) + { + SBCInitialize(); + } + SBC(const Bluetooth::Buffer& config) + : _lock() + , _supported(config.data(), config.length()) + , _actuals() + , _preset(COMPATIBLE) + , _sbcHandle(nullptr) + , _preferredBitpool(0) + , _bitpool(0) + , _bitRate(0) + , _sampleRate(0) + , _channels(0) + , _rawFrameSize(0) + , _encodedFrameSize(0) + , _frameDuration(0) + { + SBCInitialize(); + } + ~SBC() override + { + SBCDeinitialize(); + } + + public: + IAudioCodec::codectype Type() const override { + return (IAudioCodec::codectype::LC_SBC); + } + uint32_t BitRate() const override { + return (_bitRate); + } + uint16_t RawFrameSize() const override { + return (_rawFrameSize); + } + uint16_t EncodedFrameSize() const override { + return (_encodedFrameSize); + } + + uint32_t Configure(const uint8_t stream[], const uint16_t length) override; + uint32_t Configure(const StreamFormat& format, const string& settings) override; + + void Configuration(StreamFormat& format, string& settings) const override; + + uint32_t QOS(const int8_t policy); + + uint16_t Encode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outBufferSize, uint8_t outBuffer[]) const override; + + uint16_t Decode(const uint16_t inBufferSize, const uint8_t inBuffer[], + uint16_t& outBufferSize, uint8_t outBuffer[]) const override; + + uint16_t Serialize(const bool capabilities, uint8_t stream[], const uint16_t length) const override; + + private: + void Bitpool(uint8_t value); + + private: +#ifdef __DEBUG__ + void DumpConfiguration() const; + void DumpBitrateConfiguration() const; +#endif + + private: + void SBCInitialize(); + void SBCDeinitialize(); + void SBCConfigure(); + + private: + mutable Core::CriticalSection _lock; + Format _supported; + Format _actuals; + preset _preset; + void* _sbcHandle; + uint8_t _preferredBitpool; + uint8_t _bitpool; + uint32_t _bitRate; + uint32_t _sampleRate; + uint8_t _channels; + uint16_t _rawFrameSize; + uint16_t _encodedFrameSize; + uint32_t _frameDuration; + + private: + struct SBCHeader { + uint8_t frameCount; + } __attribute__((packed)); + + }; // class SBC + +} // namespace A2DP + +} // namespace Bluetooth + +} diff --git a/Source/extensions/bluetooth/bluetooth.h b/Source/extensions/bluetooth/bluetooth.h new file mode 100644 index 000000000..51a00936b --- /dev/null +++ b/Source/extensions/bluetooth/bluetooth.h @@ -0,0 +1,33 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#error "Please define a MODULE_NAME that describes the binary/library you are building." +#endif + +#include "IDriver.h" +#include "HCISocket.h" +#include "UUID.h" +#include "Debug.h" + +#ifdef __WINDOWS__ +#pragma comment(lib, "bluetooth.lib") +#endif diff --git a/Source/extensions/bluetooth/cmake/FindBluez5UtilHeaders.cmake b/Source/extensions/bluetooth/cmake/FindBluez5UtilHeaders.cmake new file mode 100644 index 000000000..3856c0fd7 --- /dev/null +++ b/Source/extensions/bluetooth/cmake/FindBluez5UtilHeaders.cmake @@ -0,0 +1,66 @@ +# - Try to find Bluez Utils Headers +# Once done this will define +# Bluez5UtilHeaders::Bluez5UtilHeaders - The bluez include directories +# +# Copyright (C) 2022 Metrological. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + +if(Bluez5UtilHeaders_FIND_QUIETLY) + set(_FIND_MODE QUIET) +elseif(Bluez5UtilHeaders_FIND_REQUIRED) + set(_FIND_MODE REQUIRED) +endif() + +set(NEEDED_BLUEZ_HEADERS + bluetooth.h + hci.h + mgmt.h + l2cap.h +) + +set(BLUEZ_INCLUDE_DIRS) + +foreach(_header ${NEEDED_BLUEZ_HEADERS}) + find_path(_header_path bluetooth/${_header}) + if(_header_path) + message(VERBOSE "Found ${_header} in ${_header_path}") + list(APPEND BLUEZ_INCLUDE_DIRS ${_header_path}) + endif() +endforeach() + +list(REMOVE_DUPLICATES BLUEZ_INCLUDE_DIRS) + +add_library(Bluez5UtilHeaders INTERFACE) + +target_include_directories(Bluez5UtilHeaders + INTERFACE + INTERFACE_INCLUDE_DIRECTORIES ${BLUEZ_INCLUDE_DIRS} +) + +add_library(Bluez5UtilHeaders::Bluez5UtilHeaders ALIAS Bluez5UtilHeaders) + +message(TRACE "BLUEZ_INCLUDE_DIRS ${BLUEZ_INCLUDE_DIRS}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Bluez5UtilHeaders DEFAULT_MSG BLUEZ_INCLUDE_DIRS) +mark_as_advanced(BLUEZ_INCLUDE_DIRS) diff --git a/Source/extensions/bluetooth/drivers/BCM43XX.cpp b/Source/extensions/bluetooth/drivers/BCM43XX.cpp new file mode 100644 index 000000000..437cbe2a5 --- /dev/null +++ b/Source/extensions/bluetooth/drivers/BCM43XX.cpp @@ -0,0 +1,417 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SerialDriver.h" +#include +#include + +namespace Thunder { + +namespace Bluetooth { + + class Broadcom43XX : public SerialDriver { + private: + Broadcom43XX() = delete; + Broadcom43XX(const Broadcom43XX&) = delete; + Broadcom43XX& operator=(const Broadcom43XX&) = delete; + + // Vendor-specyfic HCI commands + static constexpr uint16_t BCM43XX_WRITE_LOCAL_CLOCK = 0x0045; + static constexpr uint16_t BCM43XX_WRITE_LOCAL_SPEED = 0x0018; + static constexpr uint16_t BCM43XX_WRITE_LOCAL_ADDRESS = 0x0001; + static constexpr uint16_t BCM43XX_WRITE_LOCAL_FIRMWARE = 0x002e; + + static constexpr uint8_t BCM43XX_CLOCK_48 = 1; + static constexpr uint8_t BCM43XX_CLOCK_24 = 2; + static constexpr uint8_t CMD_SUCCESS = 0; + + public: + class Config : public Core::JSON::Container { + private: + Config(const Config&); + Config& operator=(const Config&); + + public: + Config() + : Core::JSON::Container() + , Port(_T("/dev/ttyAMA0")) + , Firmware(_T("/etc/firmware/")) + , SetupRate(115200) + , BaudRate(921600) + , MACAddress() + , Break(false) + , SerialAsMAC(false) + { + Add(_T("port"), &Port); + Add(_T("firmware"), &Firmware); + Add(_T("baudrate"), &BaudRate); + Add(_T("setup"), &SetupRate); + Add(_T("address"), &MACAddress); + Add(_T("break"), &Break); + Add(_T("serialmac"), &SerialAsMAC); + } + ~Config() + { + } + + public: + Core::JSON::String Port; + Core::JSON::String Firmware; + Core::JSON::DecUInt32 SetupRate; + Core::JSON::DecUInt32 BaudRate; + Core::JSON::String MACAddress; + Core::JSON::Boolean Break; + Core::JSON::Boolean SerialAsMAC; + }; + + public: + Broadcom43XX(const Config& config) + : SerialDriver(config.Port.Value(), config.SetupRate.Value(), Core::SerialPort::OFF, config.Break.Value()) + , _directory(config.Firmware.Value()) + , _name() + , _MACLength(0) + , _setupRate(config.SetupRate.Value()) + , _baudRate(config.BaudRate.Value()) + { + uint8_t max = 0; + + if (config.MACAddress.IsSet() == true) { + Bluetooth::Address address (config.MACAddress.Value().c_str()); + + if (address.IsValid() == true) { + max = std::min(address.Length(), static_cast(sizeof(_MACAddress))); + ::memcpy (_MACAddress, address.Data(), max); + } + } + else if (config.SerialAsMAC.Value() == true) { + const uint8_t* mac = GetDeviceMAC(); + max = std::min(mac[0], static_cast(sizeof(_MACAddress))); + for (uint8_t index = 1; index <= max; index++) { + _MACAddress[max - index] = mac[index]; + } + } + + if (max > 0) { + if (max < sizeof(_MACAddress)) { + ::memset(&(_MACAddress[max]), 0, sizeof(_MACAddress) - max); + } + _MACLength = sizeof(_MACAddress); + } + } + virtual ~Broadcom43XX() + { + } + + public: + const char* Initialize() + { + const char* result = nullptr; + + if (Reset() != Core::ERROR_NONE) { + ::SleepMs(500); + SetBaudRate(_baudRate); + result = "Initial reset failed!!!"; + } + + if ((result != nullptr) && (Reset() != Core::ERROR_NONE)) { + result = "Could not reset the chip to a defined state"; + } + else if (LoadName() != Core::ERROR_NONE) { + result = "Could not load the drivers name."; + } + else if (SetSpeed(_baudRate) != Core::ERROR_NONE) { + result = "Could not set the BaudRate (first time)"; + } + else { + uint16_t index = 0; + while (isalnum(_name[index++])) /* INTENTIONALLY LEFT EMPTY */ ; + uint32_t loaded = Firmware(_directory, _name.substr(0, index - 1)); + + result = nullptr; + + // It has been observed that once the firmware is loaded the name of + // the device changes from BCM43430A1 to BCM43438A1, this is due to + // a previous load, that is not nessecarely an issue :-) + if (loaded == Core::ERROR_NONE) { + // Controller speed has been reset to default speed!!! + SetBaudRate(_setupRate); + + if (Reset() != Core::ERROR_NONE) { + result = "Could not reset the device after the firmware upload"; + } + else if (SetSpeed(_baudRate) != Core::ERROR_NONE) { + result = "Could not set the BaudRate (second time)"; + } + } else if (loaded != Core::ERROR_ALREADY_CONNECTED) { + result = "Could not upload firmware."; + } + + if ((result == nullptr) && (_MACLength > 0)) { + if (MACAddress(_MACLength, _MACAddress) != Core::ERROR_NONE) { + result = "Could not set the MAC Address specified."; + } + } + + if ( (result == nullptr) && (SerialDriver::Setup(0, HCI_UART_H4) != Core::ERROR_NONE) ) { + result = "Could not set up the driver in H4 mode."; + } + } + + return (result); + } + uint32_t Reset() + { + const uint16_t command = cmd_opcode_pack(OGF_HOST_CTL, OCF_RESET); + Exchange::Response response(Exchange::COMMAND_PKT, command); + uint32_t result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, 0, nullptr), response, 1000); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to reset chip, command failure\n"); + result = Core::ERROR_GENERAL; + } + + return result; + } + + private: + string FindFirmware(const string& directory, const string& chipName) + { + string result; + Core::Directory index(directory.c_str(), "*.hcd"); + + while ((result.empty() == true) && (index.Next() == true)) { + + if (index.Name() == chipName) { + result = index.Current(); + } else if ((index.IsDirectory() == true) && (index.Name() != _T(".")) && (index.Name() != _T(".."))) { + + result = FindFirmware(index.Current(), chipName); + } + } + + return (result); + } + uint32_t LoadName() + { + const uint16_t command = cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_LOCAL_NAME); + Exchange::Response response(Exchange::COMMAND_PKT, command); + uint32_t result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, 0, nullptr), response, 500); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to read local name, command failure\n"); + result = Core::ERROR_GENERAL; + } + if (result == Core::ERROR_NONE) { + _name = string(reinterpret_cast(&(response[4]))); + } + return (result); + } + uint32_t SetClock(const uint8_t clock) + { + const uint16_t command = cmd_opcode_pack(OGF_VENDOR_CMD, BCM43XX_WRITE_LOCAL_CLOCK); + Exchange::Response response(Exchange::COMMAND_PKT, command); + uint32_t result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, 1, &clock), response, 500); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to read local name, command failure\n"); + result = Core::ERROR_GENERAL; + } + + return (result); + } + uint32_t SetSpeed(const uint32_t baudrate) + { + uint32_t result = Core::ERROR_NONE; + const uint16_t command = cmd_opcode_pack(OGF_VENDOR_CMD, BCM43XX_WRITE_LOCAL_SPEED); + + if (baudrate > 3000000) { + result = SetClock(BCM43XX_CLOCK_48); + Flush(); + } + + if (result == Core::ERROR_NONE) { + uint8_t data[6]; + + data[0] = 0x00; + data[1] = 0x00; + data[2] = static_cast(baudrate & 0xFF); + data[3] = static_cast((baudrate >> 8) & 0xFF); + data[4] = static_cast((baudrate >> 16) & 0xFF); + data[5] = static_cast((baudrate >> 24) & 0xFF); + + Exchange::Response response(Exchange::COMMAND_PKT, command); + uint32_t result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, sizeof(data), data), response, 500); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to read local name, command failure\n"); + result = Core::ERROR_GENERAL; + } else { + SetBaudRate(baudrate); + } + } + + return (result); + } + uint32_t MACAddress(const uint8_t length, const uint8_t address[]) + { + uint8_t data[6]; + ::memset(data, 0, sizeof(data)); + ::memcpy(data, address, std::min(length, static_cast(sizeof(data)))); + const uint16_t command = cmd_opcode_pack(OGF_VENDOR_CMD, BCM43XX_WRITE_LOCAL_ADDRESS); + + Exchange::Response response(Exchange::COMMAND_PKT, command); + uint32_t result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, sizeof(data), data), response, 500); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to set the MAC address\n"); + result = Core::ERROR_GENERAL; + } + + return (result); + } + uint32_t Firmware(const string& directory, const string& name) + { + uint32_t result = Core::ERROR_UNAVAILABLE; + string searchPath = Core::Directory::Normalize(directory); + string firmwareName = FindFirmware(searchPath, name + ".hcd"); + const uint16_t command = cmd_opcode_pack(OGF_VENDOR_CMD, BCM43XX_WRITE_LOCAL_FIRMWARE); + + if (firmwareName.empty() == true) { + // It has been observed that once the firmware is loaded the name of + // the device changes from BCM43430A1 to BCM43438A1, this is due to + // a previous load, that is not nessecarely an issue :-) + result = Core::ERROR_ALREADY_CONNECTED; + } else { + int fd = open(firmwareName.c_str(), O_RDONLY); + + if (fd >= 0) { + Exchange::Response response(Exchange::COMMAND_PKT, command); + result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, command, 0, nullptr), response, 500); + + Flush(); + + if ((result == Core::ERROR_NONE) && (response[3] != CMD_SUCCESS)) { + TRACE_L1("Failed to set chip to download firmware, code: %d", response[3]); + result = Core::ERROR_GENERAL; + } + + if (result == Core::ERROR_NONE) { + int loaded = 0; + uint8_t tx_buf[255]; + + /* Wait 50ms to let the firmware placed in download mode */ + SleepMs(50); + Flush(); + + while ((result == Core::ERROR_NONE) && ((loaded = read(fd, tx_buf, 3)) > 0)) { + uint16_t code = tx_buf[0] | (tx_buf[1] << 8); + uint8_t len = tx_buf[2]; + + if (read(fd, tx_buf, len) < 0) { + result = Core::ERROR_READ_ERROR; + } else { + Exchange::Response response(Exchange::COMMAND_PKT, code); + result = Exchange(Exchange::Request(Exchange::COMMAND_PKT, code, len, tx_buf), response, 500); + Flush(); + } + } + + if ((loaded != 0) && (result == Core::ERROR_NONE)) { + result = Core::ERROR_NEGATIVE_ACKNOWLEDGE; + } + + /* Wait for firmware ready */ + SleepMs(2000); + } + + close(fd); + } + } + + return (result); + } + const uint8_t* GetDeviceMAC() const + { + static uint8_t MACAddressBuffer[Core::AdapterIterator::MacSize]; + + memset(MACAddressBuffer, 0, Core::AdapterIterator::MacSize); + + Core::AdapterIterator adapters; + while ((adapters.Next() == true)) { + if (adapters.HasMAC() == true) { + adapters.MACAddress(MACAddressBuffer, Core::AdapterIterator::MacSize); + break; + } + } + + return MACAddressBuffer; + } + + private: + const string _directory; + string _name; + uint8_t _MACLength; + uint8_t _MACAddress[Core::AdapterIterator::MacSize]; + uint32_t _setupRate; + uint32_t _baudRate; + }; +} +} // namespace Thunder::Bluetooth + + +#ifdef __cplusplus +extern "C" { +#endif + +Thunder::Bluetooth::Broadcom43XX* g_driver = nullptr; + +const char* construct_bluetooth_driver(const char* input) { + const char* result = "Driver already loaded."; + + if (g_driver == nullptr) { + Thunder::Bluetooth::Broadcom43XX::Config config; + config.FromString(input); + Thunder::Bluetooth::Broadcom43XX* driver = new Thunder::Bluetooth::Broadcom43XX(config); + + + result = driver->Initialize(); + + if (result == nullptr) { + g_driver = driver; + } + else { + delete driver; + } + } + return (result); +} + +void destruct_bluetooth_driver() { + if (g_driver != nullptr) { + delete g_driver; + g_driver = nullptr; + } +} + + +#ifdef __cplusplus +} +#endif + + diff --git a/Source/extensions/bluetooth/drivers/Basic.cpp b/Source/extensions/bluetooth/drivers/Basic.cpp new file mode 100644 index 000000000..20aecaa2e --- /dev/null +++ b/Source/extensions/bluetooth/drivers/Basic.cpp @@ -0,0 +1,37 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const char* construct_bluetooth_driver(const char* /* config */) { + return (nullptr); +} + +void destruct_bluetooth_driver() { +} + + +#ifdef __cplusplus +} +#endif + diff --git a/Source/extensions/bluetooth/drivers/SerialDriver.h b/Source/extensions/bluetooth/drivers/SerialDriver.h new file mode 100644 index 000000000..30561a5bd --- /dev/null +++ b/Source/extensions/bluetooth/drivers/SerialDriver.h @@ -0,0 +1,476 @@ + /* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Module.h" + +namespace Thunder { + +namespace Bluetooth { + +// #define DUMP_FRAMES 1 + +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 +#define HCI_UART_INTEL 6 +#define HCI_UART_BCM 7 +#define HCI_UART_QCA 8 +#define HCI_UART_AG6XX 9 +#define HCI_UART_NOKIA 10 +#define HCI_UART_MRVL 11 + + class EXTERNAL SerialDriver { + private: + SerialDriver() = delete; + SerialDriver(const SerialDriver&) = delete; + SerialDriver& operator=(const SerialDriver&) = delete; + + public: + // + // Exchange holds a definitions of a request and a response for the communication with the + // HCI. See Thunder/Source/core/StreamTypeKeyValue.h for more details. + // + struct Exchange { + + enum command : uint8_t { + COMMAND_PKT = 0x01, + EVENT_PKT = 0x04 + }; + + static constexpr uint32_t BUFFERSIZE = 255; + +#ifdef DUMP_FRAMES + static void DumpFrame(const char header[], const uint8_t length, const uint8_t stream[]) + { + printf("%s ", header); + for (uint8_t index = 0; index < length; index++) { + if (index != 0) { + printf(":"); + } + printf("%02X", stream[index]); + } + printf("\n"); + } +#else +#define DumpFrame(X, Y, Z) +#endif + + class Request { + private: + Request() = delete; + Request(const Request& copy) = delete; + Request& operator=(const Request& copy) = delete; + + protected: + inline Request(const uint8_t* value) + : _command(EVENT_PKT) + , _sequence(0) + , _length(0) + , _offset(0) + , _value(value) + { + ASSERT(value != nullptr); + } + inline Request(const command& cmd, const uint16_t sequence, const uint8_t* value) + : _command(cmd) + , _sequence(sequence) + , _length(0) + , _offset(0) + , _value(value) + { + ASSERT(value != nullptr); + } + + public: + inline Request(const command& cmd, const uint16_t sequence, const uint8_t length, const uint8_t* value) + : _command(cmd) + , _sequence(sequence) + , _length(length) + , _offset(0) + , _value(value) + { + ASSERT((value != nullptr) || (length == 0)); + } + inline ~Request() + { + } + + public: + inline void Reset() + { + Request::Offset(0); + } + inline command Command() const + { + return (_command); + } + inline uint16_t Sequence() const + { + return (_sequence); + } + inline uint8_t Length() const + { + return (_length); + } + inline const uint8_t* Value() const + { + return (_value); + } + inline uint16_t Acknowledge() const + { + return (_command != EVENT_PKT ? _sequence : (_length > 2 ? (_value[1] | (_value[2] << 8)) : ~0)); + } + inline command Response() const + { + return (_command != EVENT_PKT ? _command : static_cast(_length > 0 ? _value[0] : ~0)); + } + inline uint8_t DataLength() const + { + return (_length - EventOffset()); + } + inline const uint8_t& operator[](const uint8_t index) const + { + ASSERT(index < (_length - EventOffset())); + return (_value[index - EventOffset()]); + } + inline uint16_t Serialize(uint8_t stream[], const uint16_t length) const + { + uint16_t result = 0; + + if (_offset == 0) { + _offset++; + stream[result++] = _command; + } + if ((_offset == 1) && ((length - result) > 0)) { + _offset++; + stream[result++] = static_cast((_sequence) & 0xFF); + } + if ((_offset == 2) && ((length - result) > 0)) { + _offset++; + if (_command != EVENT_PKT) { + stream[result++] = static_cast((_sequence >> 8) & 0xFF); + } + } + if ((_offset == 3) && ((length - result) > 0)) { + _offset++; + stream[result++] = _length; + } + if ((length - result) > 0) { + uint16_t copyLength = ((_length - (_offset - 4)) > (length - result) ? (length - result) : (_length - (_offset - 4))); + if (copyLength > 0) { + ::memcpy(&(stream[result]), &(_value[_offset - 4]), copyLength); + _offset += copyLength; + result += copyLength; + } + } + + DumpFrame("OUT:", result, stream); + + return (result); + } + + protected: + inline uint8_t EventOffset() const + { + return (_command != EVENT_PKT ? 0 : (_length > 2 ? 3 : _length)); + } + inline void Command(const command cmd) + { + _command = cmd; + } + inline void Sequence(const uint16_t sequence) + { + _sequence = sequence; + } + inline void Length(const uint8_t length) + { + _length = length; + } + inline void Offset(const uint16_t offset) + { + _offset = offset; + } + inline uint16_t Offset() const + { + return (_offset); + } + + private: + command _command; + uint16_t _sequence; + uint8_t _length; + mutable uint16_t _offset; + const uint8_t* _value; + }; + class Response : public Request { + private: + Response() = delete; + Response(const Response& copy) = delete; + Response& operator=(const Response& copy) = delete; + + public: +PUSH_WARNING(DISABLE_WARNING_MAYBE_UNINITIALIZED) + inline Response(const command cmd, const uint16_t sequence) + : Request(cmd, sequence, _value) + { + } +POP_WARNING() + inline ~Response() + { + } + + public: + bool Copy(const Request& buffer) + { + bool result = false; + + if ((buffer.Response() == Command()) && (buffer.Acknowledge() == Sequence())) { + uint8_t copyLength = static_cast(buffer.Length() < BUFFERSIZE ? buffer.Length() : BUFFERSIZE); + ::memcpy(_value, buffer.Value(), copyLength); + Length(copyLength); + result = true; + } + + return (result); + } + + private: + uint8_t _value[BUFFERSIZE]; + }; + class Buffer : public Request { + private: + Buffer(const Buffer& copy) = delete; + Buffer& operator=(const Buffer& copy) = delete; + + public: + inline Buffer() + : Request(_value) + , _used(0) + { + } + inline ~Buffer() + { + } + + public: + inline void Flush() { + _used = 0; + Length(0); + Offset(0); + } + inline bool Next() + { + bool result = false; + + // Clear the current selected block, move on to the nex block, return true, if + // we have a next block.. + if ((Request::Offset() > 4) && ((Request::Offset() - 4) == Request::Length())) { + Request::Offset(0); + if (_used > 0) { + uint8_t length = _used; + _used = 0; + ::memcpy(&_value[0], &(_value[Request::Length()]), length); + result = Deserialize(_value, length); + } + } + return (result); + } + inline bool Deserialize(const uint8_t stream[], const uint16_t length) + { + uint16_t result = 0; + if (Offset() < 1) { + Command(static_cast(stream[result++])); + Offset(1); + } + if ((Offset() < 2) && ((length - result) > 0)) { + Sequence(stream[result++]); + Offset(2); + } + if ((Offset() < 3) && ((length - result) > 0)) { + if (Command() != EVENT_PKT) { + uint16_t sequence = (stream[result++] << 8) | Sequence(); + Sequence(sequence); + } + Offset(3); + } + if ((Offset() < 4) && ((length - result) > 0)) { + Length(stream[result++]); + Offset(4); + } + if ((length - result) > 0) { + uint16_t copyLength = std::min(Length() - (Offset() - 4), length - result); + if (copyLength > 0) { + ::memcpy(&(_value[Offset() - 4]), &(stream[result]), copyLength); + Offset(Offset() + copyLength); + result += copyLength; + } + } + DumpFrame("IN: ", result, stream); + if (result < length) { + // TODO: This needs furhter investigation in case of failures ...... + uint16_t copyLength = std::min(static_cast((2 * BUFFERSIZE) - (Offset() - 4) - _used), static_cast(length - result)); + ::memcpy(&(_value[Offset() - 4 + _used]), &stream[result], copyLength); + _used += copyLength; + printf("Bytes in store: %d\n", _used); + } + + return ((Offset() >= 4) && ((Offset() - 4) == Length())); + } + + private: + uint16_t _used; + uint8_t _value[2 * BUFFERSIZE]; + }; + }; + + private: + class Channel : public Core::MessageExchangeType { + private: + Channel() = delete; + Channel(const Channel&) = delete; + Channel& operator=(const Channel&) = delete; + + typedef MessageExchangeType BaseClass; + + public: + Channel(SerialDriver& parent, const string& name) + : BaseClass(name) + , _parent(parent) + { + } + virtual ~Channel() + { + } + + private: + virtual void Received(const Exchange::Request& element) override + { + _parent.Received(element); + } + + private: + SerialDriver& _parent; + }; + + protected: + SerialDriver(const string& port, const uint32_t baudRate, const Core::SerialPort::FlowControl flowControl, const bool sendBreak) + : _port(*this, port) + , _flowControl(flowControl) + { + if (_port.Open(100) == Core::ERROR_NONE) { + + ToTerminal(); + _port.Link().Configuration(Core::SerialPort::Convert(baudRate), flowControl); + _port.Flush(); + + if (sendBreak == true) { + _port.Link().SendBreak(); + SleepMs(500); + } + } + else { + printf("Could not open serialport.\n"); + } + } + + public: + virtual ~SerialDriver() + { + if (_port.IsOpen() == true) { + ToTerminal(); + _port.Close(Core::infinite); + } + } + + public: + uint32_t Setup(const unsigned long flags, const int protocol) + { + _port.Flush(); + + int ttyValue = N_HCI; + if (::ioctl(static_cast(_port.Link()).Descriptor(), TIOCSETD, &ttyValue) < 0) { + TRACE_L1("Failed direct IOCTL to TIOCSETD, %d", errno); + } else if (::ioctl(static_cast(_port.Link()).Descriptor(), HCIUARTSETFLAGS, flags) < 0) { + TRACE_L1("Failed HCIUARTSETFLAGS. [flags:%lu]", flags); + } else if (::ioctl(static_cast(_port.Link()).Descriptor(), HCIUARTSETPROTO, protocol) < 0) { + TRACE_L1("Failed HCIUARTSETPROTO. [protocol:%d]", protocol); + } else { + return (Core::ERROR_NONE); + } + + return (Core::ERROR_GENERAL); + } + uint32_t Exchange(const Exchange::Request& request, Exchange::Response& response, const uint32_t allowedTime) + { + return (_port.Exchange(request, response, allowedTime)); + } + void Reconfigure(const uint32_t baudRate) + { + if (_port.IsOpen() == true) { + ToTerminal(); + _port.Close(Core::infinite); + } + if (_port.Open(100) == Core::ERROR_NONE) { + + ToTerminal(); + _port.Link().Configuration(Core::SerialPort::Convert(baudRate), _flowControl); + _port.Flush(); + } + else { + printf("Could not open serialport.\n"); + } + } + void SetBaudRate(const uint32_t baudRate) + { + _port.Link().SetBaudRate(Core::SerialPort::Convert(baudRate)); + _port.Flush(); + } + void Flush() + { + _port.Flush(); + } + virtual void Received(const Exchange::Request& element VARIABLE_IS_NOT_USED) + { + } + + private: + inline void ToTerminal() + { + int ttyValue = N_TTY; + _port.Link().Flush(); + if (::ioctl(static_cast(_port.Link()).Descriptor(), TIOCSETD, &ttyValue) < 0) { + printf("Failed direct IOCTL to TIOCSETD, %d\n", errno); + } + } + + private: + Channel _port; + Core::SerialPort::FlowControl _flowControl; + }; +} +} // namespace Thunder::Bluetooth diff --git a/Source/extensions/bluetooth/gatt/CMakeLists.txt b/Source/extensions/bluetooth/gatt/CMakeLists.txt new file mode 100644 index 000000000..f2b1fc92b --- /dev/null +++ b/Source/extensions/bluetooth/gatt/CMakeLists.txt @@ -0,0 +1,102 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) + +project(${NAMESPACE}BluetoothGATT + VERSION 1.0.0 + DESCRIPTION "Bluetooth GATT library" + LANGUAGES CXX) + +set(TARGET ${PROJECT_NAME}) +message("Setup ${TARGET} v${PROJECT_VERSION}") + +set(PUBLIC_HEADERS + GATTSocket.h + GATTProfile.h + Module.h + bluetooth_gatt.h +) + +add_library(${TARGET} + GATTSocket.cpp + GATTProfile.cpp + Module.cpp +) + +target_link_libraries(${TARGET} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ${NAMESPACE}Messaging::${NAMESPACE}Messaging +) + +set_target_properties(${TARGET} + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + PUBLIC_HEADER "${PUBLIC_HEADERS}" # specify the public headers + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) + +target_include_directories(${TARGET} + PUBLIC + $ + $ + $ + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/drivers" +) + +target_compile_options(${TARGET} + PRIVATE + -Wno-psabi + -fdiagnostics-color=always +) + +# +# From Bluez >= v5.64 the mgmt_ltk_info struct is changed due to inclusive language changes. +# https://github.com/bluez/bluez/commit/b7d6a7d25628e9b521a29a5c133fcadcedeb2102 +# +include(CheckStructHasMember) +check_struct_has_member("struct mgmt_ltk_info" central "../include/bluetooth/bluetooth.h;../include/bluetooth/mgmt.h" NO_INCLUSIVE_LANGUAGE LANGUAGE C) + +if(${NO_INCLUSIVE_LANGUAGE}) + message(VERBOSE "Your version of bluez don't uses inclusive language anymore") + target_compile_definitions(${TARGET} PUBLIC NO_INCLUSIVE_LANGUAGE) +endif() + +install( + TARGETS ${TARGET} EXPORT ${TARGET}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetooth/gatt COMPONENT ${NAMESPACE}_Development + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE} +) + +InstallPackageConfig( + TARGETS ${TARGET} + DESCRIPTION "${PROJECT_DESCRIPTION}" +) + +InstallCMakeConfig( + TARGETS ${TARGET} +) diff --git a/Source/extensions/bluetooth/gatt/GATTProfile.cpp b/Source/extensions/bluetooth/gatt/GATTProfile.cpp new file mode 100644 index 000000000..385c1a2b2 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/GATTProfile.cpp @@ -0,0 +1,326 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GATTProfile.h" + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(Bluetooth::GATTProfile::Service::Characteristic::Descriptor::type) + + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::CharacteristicAggregateFormat, _TXT("CharacteristicAggregateFormat") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::CharacteristicExtendedPropertie, _TXT("CharacteristicExtendedPropertie") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::CharacteristicPresentationFormat, _TXT("CharacteristicPresentationFormat") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::CharacteristicUserDescription, _TXT("CharacteristicUserDescription") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ClientCharacteristicConfiguration, _TXT("ClientCharacteristicConfiguration") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::EnvironmentalSensingConfiguration, _TXT("EnvironmentalSensingConfiguration") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::EnvironmentalSensingMeasurement, _TXT("EnvironmentalSensingMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::EnvironmentalSensingTriggerSetting, _TXT("EnvironmentalSensingTriggerSetting") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ExternalReportReference, _TXT("ExternalReportReference") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::NumberOfDigital, _TXT("NumberOfDigital") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ReportReference, _TXT("ReportReference") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ServerCharacteristicConfiguration, _TXT("ServerCharacteristicConfiguration") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::TimeTriggerSetting, _TXT("TimeTriggerSetting") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ValidRange, _TXT("ValidRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::Descriptor::ValueTriggerSetting, _TXT("ValueTriggerSetting") }, + +ENUM_CONVERSION_END(Bluetooth::GATTProfile::Service::Characteristic::Descriptor::type) + +ENUM_CONVERSION_BEGIN(Bluetooth::GATTProfile::Service::Characteristic::type) + + { Bluetooth::GATTProfile::Service::Characteristic::AerobicHeartRateLowerLimit, _TXT("AerobicHeartRateLowerLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::AerobicHeartRateUpperLimit, _TXT("AerobicHeartRateUpperLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::AerobicThreshold, _TXT("AerobicThreshold") }, + { Bluetooth::GATTProfile::Service::Characteristic::Age, _TXT("Age") }, + { Bluetooth::GATTProfile::Service::Characteristic::Aggregate, _TXT("Aggregate") }, + { Bluetooth::GATTProfile::Service::Characteristic::AlertCategoryID, _TXT("AlertCategoryID") }, + { Bluetooth::GATTProfile::Service::Characteristic::AlertCategoryIDBitMask, _TXT("AlertCategoryIDBitMask") }, + { Bluetooth::GATTProfile::Service::Characteristic::AlertLevel, _TXT("AlertLevel") }, + { Bluetooth::GATTProfile::Service::Characteristic::AlertNotificationControlPoint, _TXT("AlertNotificationControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::AlertStatus, _TXT("AlertStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::Altitude, _TXT("Altitude") }, + { Bluetooth::GATTProfile::Service::Characteristic::AnaerobicHeartRateLowerLimit, _TXT("AnaerobicHeartRateLowerLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::AnaerobicHeartRateUpperLimit, _TXT("AnaerobicHeartRateUpperLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::AnaerobicThreshold, _TXT("AnaerobicThreshold") }, + { Bluetooth::GATTProfile::Service::Characteristic::Analog, _TXT("Analog") }, + { Bluetooth::GATTProfile::Service::Characteristic::AnalogOutput, _TXT("AnalogOutput") }, + { Bluetooth::GATTProfile::Service::Characteristic::ApparentWindDirection, _TXT("ApparentWindDirection") }, + { Bluetooth::GATTProfile::Service::Characteristic::ApparentWindSpeed, _TXT("ApparentWindSpeed") }, + { Bluetooth::GATTProfile::Service::Characteristic::Appearance, _TXT("Appearance") }, + { Bluetooth::GATTProfile::Service::Characteristic::BarometricPressureTrend, _TXT("BarometricPressureTrend") }, + { Bluetooth::GATTProfile::Service::Characteristic::BatteryLevel, _TXT("BatteryLevel") }, + { Bluetooth::GATTProfile::Service::Characteristic::BatteryLevelState, _TXT("BatteryLevelState") }, + { Bluetooth::GATTProfile::Service::Characteristic::BatteryPowerState, _TXT("BatteryPowerState") }, + { Bluetooth::GATTProfile::Service::Characteristic::BloodPressureFeature, _TXT("BloodPressureFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::BloodPressureMeasurement, _TXT("BloodPressureMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::BodyCompositionFeature, _TXT("BodyCompositionFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::BodyCompositionMeasurement, _TXT("BodyCompositionMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::BodySensorLocation, _TXT("BodySensorLocation") }, + { Bluetooth::GATTProfile::Service::Characteristic::BondManagementControlPoint, _TXT("BondManagementControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::BondManagementFeatures, _TXT("BondManagementFeatures") }, + { Bluetooth::GATTProfile::Service::Characteristic::BootKeyboardInputReport, _TXT("BootKeyboardInputReport") }, + { Bluetooth::GATTProfile::Service::Characteristic::BootKeyboardOutputReport, _TXT("BootKeyboardOutputReport") }, + { Bluetooth::GATTProfile::Service::Characteristic::BootMouseInputReport, _TXT("BootMouseInputReport") }, + { Bluetooth::GATTProfile::Service::Characteristic::BSSControlPoint, _TXT("BSSControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::BSSResponse, _TXT("BSSResponse") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMFeature, _TXT("CGMFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMMeasurement, _TXT("CGMMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMSessionRunTime, _TXT("CGMSessionRunTime") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMSessionStartTime, _TXT("CGMSessionStartTime") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMSpecificOpsControlPoint, _TXT("CGMSpecificOpsControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::CGMStatus, _TXT("CGMStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::CrossTrainerData, _TXT("CrossTrainerData") }, + { Bluetooth::GATTProfile::Service::Characteristic::CSCFeature, _TXT("CSCFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::CSCMeasurement, _TXT("CSCMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::CurrentTime, _TXT("CurrentTime") }, + { Bluetooth::GATTProfile::Service::Characteristic::CyclingPowerControlPoint, _TXT("CyclingPowerControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::CyclingPowerFeature, _TXT("CyclingPowerFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::CyclingPowerMeasurement, _TXT("CyclingPowerMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::CyclingPowerVector, _TXT("CyclingPowerVector") }, + { Bluetooth::GATTProfile::Service::Characteristic::DatabaseChangeIncrement, _TXT("DatabaseChangeIncrement") }, + { Bluetooth::GATTProfile::Service::Characteristic::DateofBirth, _TXT("DateofBirth") }, + { Bluetooth::GATTProfile::Service::Characteristic::DateofThresholdAssessment, _TXT("DateofThresholdAssessment") }, + { Bluetooth::GATTProfile::Service::Characteristic::DateTime, _TXT("DateTime") }, + { Bluetooth::GATTProfile::Service::Characteristic::DateUTC, _TXT("DateUTC") }, + { Bluetooth::GATTProfile::Service::Characteristic::DayDateTime, _TXT("DayDateTime") }, + { Bluetooth::GATTProfile::Service::Characteristic::DayofWeek, _TXT("DayofWeek") }, + { Bluetooth::GATTProfile::Service::Characteristic::DescriptorValueChanged, _TXT("DescriptorValueChanged") }, + { Bluetooth::GATTProfile::Service::Characteristic::DewPoint, _TXT("DewPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::Digital, _TXT("Digital") }, + { Bluetooth::GATTProfile::Service::Characteristic::DigitalOutput, _TXT("DigitalOutput") }, + { Bluetooth::GATTProfile::Service::Characteristic::DSTOffset, _TXT("DSTOffset") }, + { Bluetooth::GATTProfile::Service::Characteristic::Elevation, _TXT("Elevation") }, + { Bluetooth::GATTProfile::Service::Characteristic::EmailAddress, _TXT("EmailAddress") }, + { Bluetooth::GATTProfile::Service::Characteristic::EmergencyID, _TXT("EmergencyID") }, + { Bluetooth::GATTProfile::Service::Characteristic::EmergencyText, _TXT("EmergencyText") }, + { Bluetooth::GATTProfile::Service::Characteristic::ExactTime100, _TXT("ExactTime100") }, + { Bluetooth::GATTProfile::Service::Characteristic::ExactTime256, _TXT("ExactTime256") }, + { Bluetooth::GATTProfile::Service::Characteristic::FatBurnHeartRateLowerLimit, _TXT("FatBurnHeartRateLowerLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::FatBurnHeartRateUpperLimit, _TXT("FatBurnHeartRateUpperLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::FirmwareRevisionString, _TXT("FirmwareRevisionString") }, + { Bluetooth::GATTProfile::Service::Characteristic::FirstName, _TXT("FirstName") }, + { Bluetooth::GATTProfile::Service::Characteristic::FitnessMachineControlPoint, _TXT("FitnessMachineControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::FitnessMachineFeature, _TXT("FitnessMachineFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::FitnessMachineStatus, _TXT("FitnessMachineStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::FiveZoneHeartRateLimits, _TXT("FiveZoneHeartRateLimits") }, + { Bluetooth::GATTProfile::Service::Characteristic::FloorNumber, _TXT("FloorNumber") }, + { Bluetooth::GATTProfile::Service::Characteristic::CentralAddressResolution, _TXT("CentralAddressResolution") }, + { Bluetooth::GATTProfile::Service::Characteristic::DeviceName, _TXT("DeviceName") }, + { Bluetooth::GATTProfile::Service::Characteristic::PeripheralPreferredConnectionParameters, _TXT("PeripheralPreferredConnectionParameters") }, + { Bluetooth::GATTProfile::Service::Characteristic::PeripheralPrivacyFlag, _TXT("PeripheralPrivacyFlag") }, + { Bluetooth::GATTProfile::Service::Characteristic::ReconnectionAddress, _TXT("ReconnectionAddress") }, + { Bluetooth::GATTProfile::Service::Characteristic::ServiceChanged, _TXT("ServiceChanged") }, + { Bluetooth::GATTProfile::Service::Characteristic::Gender, _TXT("Gender") }, + { Bluetooth::GATTProfile::Service::Characteristic::GlucoseFeature, _TXT("GlucoseFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::GlucoseMeasurement, _TXT("GlucoseMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::GlucoseMeasurementContext, _TXT("GlucoseMeasurementContext") }, + { Bluetooth::GATTProfile::Service::Characteristic::GustFactor, _TXT("GustFactor") }, + { Bluetooth::GATTProfile::Service::Characteristic::HardwareRevisionString, _TXT("HardwareRevisionString") }, + { Bluetooth::GATTProfile::Service::Characteristic::HeartRateControlPoint, _TXT("HeartRateControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::HeartRateMax, _TXT("HeartRateMax") }, + { Bluetooth::GATTProfile::Service::Characteristic::HeartRateMeasurement, _TXT("HeartRateMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::HeatIndex, _TXT("HeatIndex") }, + { Bluetooth::GATTProfile::Service::Characteristic::Height, _TXT("Height") }, + { Bluetooth::GATTProfile::Service::Characteristic::HIDControlPoint, _TXT("HIDControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::HIDInformation, _TXT("HIDInformation") }, + { Bluetooth::GATTProfile::Service::Characteristic::HipCircumference, _TXT("HipCircumference") }, + { Bluetooth::GATTProfile::Service::Characteristic::HTTPControlPoint, _TXT("HTTPControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::HTTPEntityBody, _TXT("HTTPEntityBody") }, + { Bluetooth::GATTProfile::Service::Characteristic::HTTPHeaders, _TXT("HTTPHeaders") }, + { Bluetooth::GATTProfile::Service::Characteristic::HTTPStatusCode, _TXT("HTTPStatusCode") }, + { Bluetooth::GATTProfile::Service::Characteristic::HTTPSSecurity, _TXT("HTTPSSecurity") }, + { Bluetooth::GATTProfile::Service::Characteristic::Humidity, _TXT("Humidity") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDAnnunciationStatus, _TXT("IDDAnnunciationStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDCommandControlPoint, _TXT("IDDCommandControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDCommandData, _TXT("IDDCommandData") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDFeatures, _TXT("IDDFeatures") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDHistoryData, _TXT("IDDHistoryData") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDRecordAccessControlPoint, _TXT("IDDRecordAccessControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDStatus, _TXT("IDDStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDStatusChanged, _TXT("IDDStatusChanged") }, + { Bluetooth::GATTProfile::Service::Characteristic::IDDStatusReaderControlPoint, _TXT("IDDStatusReaderControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::IndoorBikeData, _TXT("IndoorBikeData") }, + { Bluetooth::GATTProfile::Service::Characteristic::IndoorPositioningConfiguration, _TXT("IndoorPositioningConfiguration") }, + { Bluetooth::GATTProfile::Service::Characteristic::IntermediateCuffPressure, _TXT("IntermediateCuffPressure") }, + { Bluetooth::GATTProfile::Service::Characteristic::IntermediateTemperature, _TXT("IntermediateTemperature") }, + { Bluetooth::GATTProfile::Service::Characteristic::Irradiance, _TXT("Irradiance") }, + { Bluetooth::GATTProfile::Service::Characteristic::Language, _TXT("Language") }, + { Bluetooth::GATTProfile::Service::Characteristic::LastName, _TXT("LastName") }, + { Bluetooth::GATTProfile::Service::Characteristic::Latitude, _TXT("Latitude") }, + { Bluetooth::GATTProfile::Service::Characteristic::LNControlPoint, _TXT("LNControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::LNFeature, _TXT("LNFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::LocalEastCoordinate, _TXT("LocalEastCoordinate") }, + { Bluetooth::GATTProfile::Service::Characteristic::LocalNorthCoordinate, _TXT("LocalNorthCoordinate") }, + { Bluetooth::GATTProfile::Service::Characteristic::LocalTimeInformation, _TXT("LocalTimeInformation") }, + { Bluetooth::GATTProfile::Service::Characteristic::LocationandSpeedCharacteristic, _TXT("LocationandSpeedCharacteristic") }, + { Bluetooth::GATTProfile::Service::Characteristic::LocationName, _TXT("LocationName") }, + { Bluetooth::GATTProfile::Service::Characteristic::Longitude, _TXT("Longitude") }, + { Bluetooth::GATTProfile::Service::Characteristic::MagneticDeclination, _TXT("MagneticDeclination") }, + { Bluetooth::GATTProfile::Service::Characteristic::MagneticFluxDensity_2D, _TXT("MagneticFluxDensity_2D") }, + { Bluetooth::GATTProfile::Service::Characteristic::MagneticFluxDensity_3D, _TXT("MagneticFluxDensity_3D") }, + { Bluetooth::GATTProfile::Service::Characteristic::ManufacturerNameString, _TXT("ManufacturerNameString") }, + { Bluetooth::GATTProfile::Service::Characteristic::MaximumRecommendedHeartRate, _TXT("MaximumRecommendedHeartRate") }, + { Bluetooth::GATTProfile::Service::Characteristic::MeasurementInterval, _TXT("MeasurementInterval") }, + { Bluetooth::GATTProfile::Service::Characteristic::ModelNumberString, _TXT("ModelNumberString") }, + { Bluetooth::GATTProfile::Service::Characteristic::Navigation, _TXT("Navigation") }, + { Bluetooth::GATTProfile::Service::Characteristic::NetworkAvailability, _TXT("NetworkAvailability") }, + { Bluetooth::GATTProfile::Service::Characteristic::NewAlert, _TXT("NewAlert") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectActionControlPoint, _TXT("ObjectActionControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectChanged, _TXT("ObjectChanged") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectFirst_Created, _TXT("ObjectFirst_Created") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectID, _TXT("ObjectID") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectLast_Modified, _TXT("ObjectLast_Modified") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectListControlPoint, _TXT("ObjectListControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectListFilter, _TXT("ObjectListFilter") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectName, _TXT("ObjectName") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectProperties, _TXT("ObjectProperties") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectSize, _TXT("ObjectSize") }, + { Bluetooth::GATTProfile::Service::Characteristic::ObjectType, _TXT("ObjectType") }, + { Bluetooth::GATTProfile::Service::Characteristic::OTSFeature, _TXT("OTSFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::PLXContinuousMeasurementCharacteristic, _TXT("PLXContinuousMeasurementCharacteristic") }, + { Bluetooth::GATTProfile::Service::Characteristic::PLXFeatures, _TXT("PLXFeatures") }, + { Bluetooth::GATTProfile::Service::Characteristic::PLXSpot_CheckMeasurement, _TXT("PLXSpot_CheckMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::PnPID, _TXT("PnPID") }, + { Bluetooth::GATTProfile::Service::Characteristic::PollenConcentration, _TXT("PollenConcentration") }, + { Bluetooth::GATTProfile::Service::Characteristic::Position2D, _TXT("Position2D") }, + { Bluetooth::GATTProfile::Service::Characteristic::Position3D, _TXT("Position3D") }, + { Bluetooth::GATTProfile::Service::Characteristic::PositionQuality, _TXT("PositionQuality") }, + { Bluetooth::GATTProfile::Service::Characteristic::Pressure, _TXT("Pressure") }, + { Bluetooth::GATTProfile::Service::Characteristic::ProtocolMode, _TXT("ProtocolMode") }, + { Bluetooth::GATTProfile::Service::Characteristic::PulseOximetryControlPoint, _TXT("PulseOximetryControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::Rainfall, _TXT("Rainfall") }, + { Bluetooth::GATTProfile::Service::Characteristic::RCFeature, _TXT("RCFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::RCSettings, _TXT("RCSettings") }, + { Bluetooth::GATTProfile::Service::Characteristic::ReconnectionConfigurationControlPoint, _TXT("ReconnectionConfigurationControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::RecordAccessControlPoint, _TXT("RecordAccessControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::RegulatoryCertificationDataList, _TXT("RegulatoryCertificationDataList") }, + { Bluetooth::GATTProfile::Service::Characteristic::ReferenceTimeInformation, _TXT("ReferenceTimeInformation") }, + { Bluetooth::GATTProfile::Service::Characteristic::Removable, _TXT("Removable") }, + { Bluetooth::GATTProfile::Service::Characteristic::Report, _TXT("Report") }, + { Bluetooth::GATTProfile::Service::Characteristic::ReportMap, _TXT("ReportMap") }, + { Bluetooth::GATTProfile::Service::Characteristic::ResolvablePrivateAddressOnly, _TXT("ResolvablePrivateAddressOnly") }, + { Bluetooth::GATTProfile::Service::Characteristic::RestingHeartRate, _TXT("RestingHeartRate") }, + { Bluetooth::GATTProfile::Service::Characteristic::RingerControlpoint, _TXT("RingerControlpoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::RingerSetting, _TXT("RingerSetting") }, + { Bluetooth::GATTProfile::Service::Characteristic::RowerData, _TXT("RowerData") }, + { Bluetooth::GATTProfile::Service::Characteristic::RSCFeature, _TXT("RSCFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::RSCMeasurement, _TXT("RSCMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::SCControlPoint, _TXT("SCControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::ScanIntervalWindow, _TXT("ScanIntervalWindow") }, + { Bluetooth::GATTProfile::Service::Characteristic::ScanRefresh, _TXT("ScanRefresh") }, + { Bluetooth::GATTProfile::Service::Characteristic::ScientificTemperatureCelsius, _TXT("ScientificTemperatureCelsius") }, + { Bluetooth::GATTProfile::Service::Characteristic::SecondaryTimeZone, _TXT("SecondaryTimeZone") }, + { Bluetooth::GATTProfile::Service::Characteristic::SensorLocation, _TXT("SensorLocation") }, + { Bluetooth::GATTProfile::Service::Characteristic::SerialNumberString, _TXT("SerialNumberString") }, + { Bluetooth::GATTProfile::Service::Characteristic::ServiceRequired, _TXT("ServiceRequired") }, + { Bluetooth::GATTProfile::Service::Characteristic::SoftwareRevisionString, _TXT("SoftwareRevisionString") }, + { Bluetooth::GATTProfile::Service::Characteristic::SportTypeforAerobicandAnaerobicThresholds, _TXT("SportTypeforAerobicandAnaerobicThresholds") }, + { Bluetooth::GATTProfile::Service::Characteristic::StairClimberData, _TXT("StairClimberData") }, + { Bluetooth::GATTProfile::Service::Characteristic::StepClimberData, _TXT("StepClimberData") }, + { Bluetooth::GATTProfile::Service::Characteristic::String, _TXT("String") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedHeartRateRange, _TXT("SupportedHeartRateRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedInclinationRange, _TXT("SupportedInclinationRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedNewAlertCategory, _TXT("SupportedNewAlertCategory") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedPowerRange, _TXT("SupportedPowerRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedResistanceLevelRange, _TXT("SupportedResistanceLevelRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedSpeedRange, _TXT("SupportedSpeedRange") }, + { Bluetooth::GATTProfile::Service::Characteristic::SupportedUnreadAlertCategory, _TXT("SupportedUnreadAlertCategory") }, + { Bluetooth::GATTProfile::Service::Characteristic::SystemID, _TXT("SystemID") }, + { Bluetooth::GATTProfile::Service::Characteristic::TDSControlPoint, _TXT("TDSControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::Temperature, _TXT("Temperature") }, + { Bluetooth::GATTProfile::Service::Characteristic::TemperatureCelsius, _TXT("TemperatureCelsius") }, + { Bluetooth::GATTProfile::Service::Characteristic::TemperatureFahrenheit, _TXT("TemperatureFahrenheit") }, + { Bluetooth::GATTProfile::Service::Characteristic::TemperatureMeasurement, _TXT("TemperatureMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::TemperatureType, _TXT("TemperatureType") }, + { Bluetooth::GATTProfile::Service::Characteristic::ThreeZoneHeartRateLimits, _TXT("ThreeZoneHeartRateLimits") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeAccuracy, _TXT("TimeAccuracy") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeBroadcast, _TXT("TimeBroadcast") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeSource, _TXT("TimeSource") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeUpdateControlPoint, _TXT("TimeUpdateControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeUpdateState, _TXT("TimeUpdateState") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimewithDST, _TXT("TimewithDST") }, + { Bluetooth::GATTProfile::Service::Characteristic::TimeZone, _TXT("TimeZone") }, + { Bluetooth::GATTProfile::Service::Characteristic::TrainingStatus, _TXT("TrainingStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::TreadmillData, _TXT("TreadmillData") }, + { Bluetooth::GATTProfile::Service::Characteristic::TrueWindDirection, _TXT("TrueWindDirection") }, + { Bluetooth::GATTProfile::Service::Characteristic::TrueWindSpeed, _TXT("TrueWindSpeed") }, + { Bluetooth::GATTProfile::Service::Characteristic::TwoZoneHeartRateLimit, _TXT("TwoZoneHeartRateLimit") }, + { Bluetooth::GATTProfile::Service::Characteristic::TxPowerLevel, _TXT("TxPowerLevel") }, + { Bluetooth::GATTProfile::Service::Characteristic::Uncertainty, _TXT("Uncertainty") }, + { Bluetooth::GATTProfile::Service::Characteristic::UnreadAlertStatus, _TXT("UnreadAlertStatus") }, + { Bluetooth::GATTProfile::Service::Characteristic::URI, _TXT("URI") }, + { Bluetooth::GATTProfile::Service::Characteristic::UserControlPoint, _TXT("UserControlPoint") }, + { Bluetooth::GATTProfile::Service::Characteristic::UserIndex, _TXT("UserIndex") }, + { Bluetooth::GATTProfile::Service::Characteristic::UVIndex, _TXT("UVIndex") }, + { Bluetooth::GATTProfile::Service::Characteristic::VO2Max, _TXT("VO2Max") }, + { Bluetooth::GATTProfile::Service::Characteristic::WaistCircumference, _TXT("WaistCircumference") }, + { Bluetooth::GATTProfile::Service::Characteristic::Weight, _TXT("Weight") }, + { Bluetooth::GATTProfile::Service::Characteristic::WeightMeasurement, _TXT("WeightMeasurement") }, + { Bluetooth::GATTProfile::Service::Characteristic::WeightScaleFeature, _TXT("WeightScaleFeature") }, + { Bluetooth::GATTProfile::Service::Characteristic::WindChill, _TXT("WindChill") }, +ENUM_CONVERSION_END(Bluetooth::GATTProfile::Service::Characteristic::type) + +ENUM_CONVERSION_BEGIN(Bluetooth::GATTProfile::Service::type) + + { Bluetooth::GATTProfile::Service::GenericAccess, _TXT("Generic Access") }, + { Bluetooth::GATTProfile::Service::AlertNotificationService, _TXT("Alert Notification Service") }, + { Bluetooth::GATTProfile::Service::AutomationIO, _TXT("Automation IO") }, + { Bluetooth::GATTProfile::Service::BatteryService, _TXT("Battery Service") }, + { Bluetooth::GATTProfile::Service::BinarySensor, _TXT("Binary Sensor") }, + { Bluetooth::GATTProfile::Service::BloodPressure, _TXT("Blood Pressure") }, + { Bluetooth::GATTProfile::Service::BodyComposition, _TXT("Body Composition") }, + { Bluetooth::GATTProfile::Service::BondManagementService, _TXT("Bond Management Service") }, + { Bluetooth::GATTProfile::Service::ContinuousGlucoseMonitoring, _TXT("Continuous Glucose Monitoring") }, + { Bluetooth::GATTProfile::Service::CurrentTimeService, _TXT("Current Time Service") }, + { Bluetooth::GATTProfile::Service::CyclingPower, _TXT("Cycling Power") }, + { Bluetooth::GATTProfile::Service::CyclingSpeedAndCadence, _TXT("Cycling Speed and Cadence") }, + { Bluetooth::GATTProfile::Service::DeviceInformation, _TXT("Device Information") }, + { Bluetooth::GATTProfile::Service::EmergencyConfiguration, _TXT("Emergency Configuration") }, + { Bluetooth::GATTProfile::Service::EnvironmentalSensing, _TXT("Environmental Sensing") }, + { Bluetooth::GATTProfile::Service::FitnessMachine, _TXT("Fitness Machine") }, + { Bluetooth::GATTProfile::Service::GenericAttribute, _TXT("Generic Attribute") }, + { Bluetooth::GATTProfile::Service::Glucose, _TXT("Glucose") }, + { Bluetooth::GATTProfile::Service::HealthThermometer, _TXT("Health Thermometer") }, + { Bluetooth::GATTProfile::Service::HeartRate, _TXT("Heart Rate") }, + { Bluetooth::GATTProfile::Service::HTTPProxy, _TXT("HTTP Proxy") }, + { Bluetooth::GATTProfile::Service::HumanInterfaceDevice, _TXT("Human Interface Device") }, + { Bluetooth::GATTProfile::Service::ImmediateAlert, _TXT("Immediate Alert") }, + { Bluetooth::GATTProfile::Service::IndoorPositioning, _TXT("Indoor Positioning") }, + { Bluetooth::GATTProfile::Service::InsulinDelivery, _TXT("Insulin Delivery") }, + { Bluetooth::GATTProfile::Service::InternetProtocolSupport, _TXT("Internet Protocol Support") }, + { Bluetooth::GATTProfile::Service::LinkLoss, _TXT("Link Loss") }, + { Bluetooth::GATTProfile::Service::LocationAndNavigation, _TXT("Location and Navigation") }, + { Bluetooth::GATTProfile::Service::MeshProvisioningService, _TXT("Mesh Provisioning Service") }, + { Bluetooth::GATTProfile::Service::MeshProxyService, _TXT("Mesh Proxy Service") }, + { Bluetooth::GATTProfile::Service::NextDSTChangeService, _TXT("Next DST Change Service") }, + { Bluetooth::GATTProfile::Service::ObjectTransferService, _TXT("ObjectTransferService") }, + { Bluetooth::GATTProfile::Service::PhoneAlertStatusService, _TXT("Phone Alert Status Service") }, + { Bluetooth::GATTProfile::Service::PulseOximeterService, _TXT("Pulse Oximeter Service") }, + { Bluetooth::GATTProfile::Service::ReconnectionConfiguration, _TXT("Reconnection Configuration") }, + { Bluetooth::GATTProfile::Service::ReferenceTimeUpdateService, _TXT("Reference Time Update Service") }, + { Bluetooth::GATTProfile::Service::RunningSpeedAndCadence, _TXT("Running Speed and Cadence") }, + { Bluetooth::GATTProfile::Service::ScanParameters, _TXT("Scan Parameters") }, + { Bluetooth::GATTProfile::Service::TransportDiscovery, _TXT("Transport Discovery") }, + { Bluetooth::GATTProfile::Service::TxPower, _TXT("Tx Power") }, + { Bluetooth::GATTProfile::Service::UserData, _TXT("User Data") }, + { Bluetooth::GATTProfile::Service::WeightScale, _TXT("Weight Scale") }, + +ENUM_CONVERSION_END(Bluetooth::GATTProfile::Service::type) + +} // namespace Thunder + diff --git a/Source/extensions/bluetooth/gatt/GATTProfile.h b/Source/extensions/bluetooth/gatt/GATTProfile.h new file mode 100644 index 000000000..d014ac919 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/GATTProfile.h @@ -0,0 +1,883 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include "GATTSocket.h" + +namespace Thunder { + +namespace Bluetooth { + + class EXTERNAL GATTProfile { + private: + static constexpr uint16_t PRIMARY_SERVICE_UUID = 0x2800; + static constexpr uint16_t CHARACTERISTICS_UUID = 0x2803; + + public: + class EXTERNAL Service { + public: + enum type : uint16_t { + GenericAccess = 0x1800, + AlertNotificationService = 0x1811, + AutomationIO = 0x1815, + BatteryService = 0x180F, + BinarySensor = 0x183B, + BloodPressure = 0x1810, + BodyComposition = 0x181B, + BondManagementService = 0x181E, + ContinuousGlucoseMonitoring = 0x181F, + CurrentTimeService = 0x1805, + CyclingPower = 0x1818, + CyclingSpeedAndCadence = 0x1816, + DeviceInformation = 0x180A, + EmergencyConfiguration = 0x183C, + EnvironmentalSensing = 0x181A, + FitnessMachine = 0x1826, + GenericAttribute = 0x1801, + Glucose = 0x1808, + HealthThermometer = 0x1809, + HeartRate = 0x180D, + HTTPProxy = 0x1823, + HumanInterfaceDevice = 0x1812, + ImmediateAlert = 0x1802, + IndoorPositioning = 0x1821, + InsulinDelivery = 0x183A, + InternetProtocolSupport = 0x1820, + LinkLoss = 0x1803, + LocationAndNavigation = 0x1819, + MeshProvisioningService = 0x1827, + MeshProxyService = 0x1828, + NextDSTChangeService = 0x1807, + ObjectTransferService = 0x1825, + PhoneAlertStatusService = 0x180E, + PulseOximeterService = 0x1822, + ReconnectionConfiguration = 0x1829, + ReferenceTimeUpdateService = 0x1806, + RunningSpeedAndCadence = 0x1814, + ScanParameters = 0x1813, + TransportDiscovery = 0x1824, + TxPower = 0x1804, + UserData = 0x181C, + WeightScale = 0x181D, + }; + + class EXTERNAL Characteristic { + public: + enum type : uint16_t { + + AerobicHeartRateLowerLimit = 0x2A7E, + AerobicHeartRateUpperLimit = 0x2A84, + AerobicThreshold = 0x2A7F, + Age = 0x2A80, + Aggregate = 0x2A5A, + AlertCategoryID = 0x2A43, + AlertCategoryIDBitMask = 0x2A42, + AlertLevel = 0x2A06, + AlertNotificationControlPoint = 0x2A44, + AlertStatus = 0x2A3F, + Altitude = 0x2AB3, + AnaerobicHeartRateLowerLimit = 0x2A81, + AnaerobicHeartRateUpperLimit = 0x2A82, + AnaerobicThreshold = 0x2A83, + Analog = 0x2A58, + AnalogOutput = 0x2A59, + ApparentWindDirection = 0x2A73, + ApparentWindSpeed = 0x2A72, + Appearance = 0x2A01, + BarometricPressureTrend = 0x2AA3, + BatteryLevel = 0x2A19, + BatteryLevelState = 0x2A1B, + BatteryPowerState = 0x2A1A, + BloodPressureFeature = 0x2A49, + BloodPressureMeasurement = 0x2A35, + BodyCompositionFeature = 0x2A9B, + BodyCompositionMeasurement = 0x2A9C, + BodySensorLocation = 0x2A38, + BondManagementControlPoint = 0x2AA4, + BondManagementFeatures = 0x2AA5, + BootKeyboardInputReport = 0x2A22, + BootKeyboardOutputReport = 0x2A32, + BootMouseInputReport = 0x2A33, + BSSControlPoint = 0x2B2B, + BSSResponse = 0x2B2C, + CGMFeature = 0x2AA8, + CGMMeasurement = 0x2AA7, + CGMSessionRunTime = 0x2AAB, + CGMSessionStartTime = 0x2AAA, + CGMSpecificOpsControlPoint = 0x2AAC, + CGMStatus = 0x2AA9, + CrossTrainerData = 0x2ACE, + CSCFeature = 0x2A5C, + CSCMeasurement = 0x2A5B, + CurrentTime = 0x2A2B, + CyclingPowerControlPoint = 0x2A66, + CyclingPowerFeature = 0x2A65, + CyclingPowerMeasurement = 0x2A63, + CyclingPowerVector = 0x2A64, + DatabaseChangeIncrement = 0x2A99, + DateofBirth = 0x2A85, + DateofThresholdAssessment = 0x2A86, + DateTime = 0x2A08, + DateUTC = 0x2AED, + DayDateTime = 0x2A0A, + DayofWeek = 0x2A09, + DescriptorValueChanged = 0x2A7D, + DewPoint = 0x2A7B, + Digital = 0x2A56, + DigitalOutput = 0x2A57, + DSTOffset = 0x2A0D, + Elevation = 0x2A6C, + EmailAddress = 0x2A87, + EmergencyID = 0x2B2D, + EmergencyText = 0x2B2E, + ExactTime100 = 0x2A0B, + ExactTime256 = 0x2A0C, + FatBurnHeartRateLowerLimit = 0x2A88, + FatBurnHeartRateUpperLimit = 0x2A89, + FirmwareRevisionString = 0x2A26, + FirstName = 0x2A8A, + FitnessMachineControlPoint = 0x2AD9, + FitnessMachineFeature = 0x2ACC, + FitnessMachineStatus = 0x2ADA, + FiveZoneHeartRateLimits = 0x2A8B, + FloorNumber = 0x2AB2, + CentralAddressResolution = 0x2AA6, + DeviceName = 0x2A00, + PeripheralPreferredConnectionParameters = 0x2A04, + PeripheralPrivacyFlag = 0x2A02, + ReconnectionAddress = 0x2A03, + ServiceChanged = 0x2A05, + Gender = 0x2A8C, + GlucoseFeature = 0x2A51, + GlucoseMeasurement = 0x2A18, + GlucoseMeasurementContext = 0x2A34, + GustFactor = 0x2A74, + HardwareRevisionString = 0x2A27, + HeartRateControlPoint = 0x2A39, + HeartRateMax = 0x2A8D, + HeartRateMeasurement = 0x2A37, + HeatIndex = 0x2A7A, + Height = 0x2A8E, + HIDControlPoint = 0x2A4C, + HIDInformation = 0x2A4A, + HipCircumference = 0x2A8F, + HTTPControlPoint = 0x2ABA, + HTTPEntityBody = 0x2AB9, + HTTPHeaders = 0x2AB7, + HTTPStatusCode = 0x2AB8, + HTTPSSecurity = 0x2ABB, + Humidity = 0x2A6F, + IDDAnnunciationStatus = 0x2B22, + IDDCommandControlPoint = 0x2B25, + IDDCommandData = 0x2B26, + IDDFeatures = 0x2B23, + IDDHistoryData = 0x2B28, + IDDRecordAccessControlPoint = 0x2B27, + IDDStatus = 0x2B21, + IDDStatusChanged = 0x2B20, + IDDStatusReaderControlPoint = 0x2B24, + IndoorBikeData = 0x2AD2, + IndoorPositioningConfiguration = 0x2AAD, + IntermediateCuffPressure = 0x2A36, + IntermediateTemperature = 0x2A1E, + Irradiance = 0x2A77, + Language = 0x2AA2, + LastName = 0x2A90, + Latitude = 0x2AAE, + LNControlPoint = 0x2A6B, + LNFeature = 0x2A6A, + LocalEastCoordinate = 0x2AB1, + LocalNorthCoordinate = 0x2AB0, + LocalTimeInformation = 0x2A0F, + LocationandSpeedCharacteristic = 0x2A67, + LocationName = 0x2AB5, + Longitude = 0x2AAF, + MagneticDeclination = 0x2A2C, + MagneticFluxDensity_2D = 0x2AA0, + MagneticFluxDensity_3D = 0x2AA1, + ManufacturerNameString = 0x2A29, + MaximumRecommendedHeartRate = 0x2A91, + MeasurementInterval = 0x2A21, + ModelNumberString = 0x2A24, + Navigation = 0x2A68, + NetworkAvailability = 0x2A3E, + NewAlert = 0x2A46, + ObjectActionControlPoint = 0x2AC5, + ObjectChanged = 0x2AC8, + ObjectFirst_Created = 0x2AC1, + ObjectID = 0x2AC3, + ObjectLast_Modified = 0x2AC2, + ObjectListControlPoint = 0x2AC6, + ObjectListFilter = 0x2AC7, + ObjectName = 0x2ABE, + ObjectProperties = 0x2AC4, + ObjectSize = 0x2AC0, + ObjectType = 0x2ABF, + OTSFeature = 0x2ABD, + PLXContinuousMeasurementCharacteristic = 0x2A5F, + PLXFeatures = 0x2A60, + PLXSpot_CheckMeasurement = 0x2A5E, + PnPID = 0x2A50, + PollenConcentration = 0x2A75, + Position2D = 0x2A2F, + Position3D = 0x2A30, + PositionQuality = 0x2A69, + Pressure = 0x2A6D, + ProtocolMode = 0x2A4E, + PulseOximetryControlPoint = 0x2A62, + Rainfall = 0x2A78, + RCFeature = 0x2B1D, + RCSettings = 0x2B1E, + ReconnectionConfigurationControlPoint = 0x2B1F, + RecordAccessControlPoint = 0x2A52, + RegulatoryCertificationDataList = 0x2A2A, + ReferenceTimeInformation = 0x2A14, + Removable = 0x2A3A, + Report = 0x2A4D, + ReportMap = 0x2A4B, + ResolvablePrivateAddressOnly = 0x2AC9, + RestingHeartRate = 0x2A92, + RingerControlpoint = 0x2A40, + RingerSetting = 0x2A41, + RowerData = 0x2AD1, + RSCFeature = 0x2A54, + RSCMeasurement = 0x2A53, + SCControlPoint = 0x2A55, + ScanIntervalWindow = 0x2A4F, + ScanRefresh = 0x2A31, + ScientificTemperatureCelsius = 0x2A3C, + SecondaryTimeZone = 0x2A10, + SensorLocation = 0x2A5D, + SerialNumberString = 0x2A25, + ServiceRequired = 0x2A3B, + SoftwareRevisionString = 0x2A28, + SportTypeforAerobicandAnaerobicThresholds = 0x2A93, + StairClimberData = 0x2AD0, + StepClimberData = 0x2ACF, + String = 0x2A3D, + SupportedHeartRateRange = 0x2AD7, + SupportedInclinationRange = 0x2AD5, + SupportedNewAlertCategory = 0x2A47, + SupportedPowerRange = 0x2AD8, + SupportedResistanceLevelRange = 0x2AD6, + SupportedSpeedRange = 0x2AD4, + SupportedUnreadAlertCategory = 0x2A48, + SystemID = 0x2A23, + TDSControlPoint = 0x2ABC, + Temperature = 0x2A6E, + TemperatureCelsius = 0x2A1F, + TemperatureFahrenheit = 0x2A20, + TemperatureMeasurement = 0x2A1C, + TemperatureType = 0x2A1D, + ThreeZoneHeartRateLimits = 0x2A94, + TimeAccuracy = 0x2A12, + TimeBroadcast = 0x2A15, + TimeSource = 0x2A13, + TimeUpdateControlPoint = 0x2A16, + TimeUpdateState = 0x2A17, + TimewithDST = 0x2A11, + TimeZone = 0x2A0E, + TrainingStatus = 0x2AD3, + TreadmillData = 0x2ACD, + TrueWindDirection = 0x2A71, + TrueWindSpeed = 0x2A70, + TwoZoneHeartRateLimit = 0x2A95, + TxPowerLevel = 0x2A07, + Uncertainty = 0x2AB4, + UnreadAlertStatus = 0x2A45, + URI = 0x2AB6, + UserControlPoint = 0x2A9F, + UserIndex = 0x2A9A, + UVIndex = 0x2A76, + VO2Max = 0x2A96, + WaistCircumference = 0x2A97, + Weight = 0x2A98, + WeightMeasurement = 0x2A9D, + WeightScaleFeature = 0x2A9E, + WindChill = 0x2A79 + }; + + class EXTERNAL Descriptor { + public: + enum type : uint16_t { + CharacteristicAggregateFormat = 0x2905, + CharacteristicExtendedPropertie = 0x2900, + CharacteristicPresentationFormat = 0x2904, + CharacteristicUserDescription = 0x2901, + ClientCharacteristicConfiguration = 0x2902, + EnvironmentalSensingConfiguration = 0x290B, + EnvironmentalSensingMeasurement = 0x290C, + EnvironmentalSensingTriggerSetting = 0x290D, + ExternalReportReference = 0x2907, + NumberOfDigital = 0x2909, + ReportReference = 0x2908, + ServerCharacteristicConfiguration = 0x2903, + TimeTriggerSetting = 0x290E, + ValidRange = 0x2906, + ValueTriggerSetting = 0x290A + }; + + public: + Descriptor(const Descriptor&) = delete; + Descriptor& operator= (const Descriptor&) = delete; + + Descriptor(const uint16_t handle, const UUID& uuid) + : _handle(handle) + , _uuid(uuid) { + } + ~Descriptor() { + } + + public: + bool operator== (const UUID& id) const { + return (id == _uuid); + } + bool operator!= (const UUID& id) const { + return (!operator== (id)); + } + const UUID& Type() const { + return (_uuid); + } + string Name() const { + string result; + if (_uuid.HasShort() == false) { + result = _uuid.ToString(); + } + else { + type input = static_cast(_uuid.Short()); + Core::EnumerateType value (input); + result = (value.IsSet() == true ? string(value.Data()) : _uuid.ToString(false)); + } + return (result); + } + uint16_t Handle() const { + return (_handle); + } + + private: + uint16_t _handle; + UUID _uuid; + }; + + public: + typedef Core::IteratorType< const std::list, const Descriptor&, std::list::const_iterator> Iterator; + + Characteristic(const Characteristic&) = delete; + Characteristic& operator= (const Characteristic&) = delete; + + Characteristic(const uint16_t end, const uint8_t rights, const uint16_t value, const UUID& attribute) + : _handle(value) + , _rights(rights) + , _end(end) + , _type(attribute) + , _error(0) + , _value() { + } + ~Characteristic() { + } + + public: + bool operator== (const UUID& id) const { + return (id == _type); + } + bool operator!= (const UUID& id) const { + return (!operator== (id)); + } + uint8_t Error() const { + return (_error); + } + string ToString() const { + string result; + result.reserve(_value.length() + 1); + for (const char& c : _value) { + if (::isprint(c)) { + result = result + c; + } + else { + result = result + '.'; + } + } + return (result); + } + const UUID& Type() const { + return (_type); + } + uint8_t Rights() const { + return (_rights); + } + string Name() const { + string result; + if (_type.HasShort() == false) { + result = _type.ToString(); + } + else { + type input = static_cast(_type.Short()); + Core::EnumerateType value (input); + result = (value.IsSet() == true ? string(value.Data()) : _type.ToString(false)); + } + return (result); + } + Iterator Descriptors() const { + return (Iterator(_descriptors)); + } + const Descriptor* operator[] (const UUID& id) const { + std::list::const_iterator index (_descriptors.begin()); + + while ((index != _descriptors.end()) && (*index != id)) { index++; } + + return (index != _descriptors.end() ? &(*index) : nullptr); + } + uint16_t Handle() const { + return (_handle); + } + uint16_t Max() const { + return (_end); + } + + private: + friend class GATTProfile; + void Descriptors (GATTSocket::Command::Response& response) { + while (response.Next() == true) { + UUID descriptor(response.Attribute()); + _descriptors.emplace_back(response.Handle(), descriptor); + } + } + uint16_t Value(GATTSocket::Command::Response& response) { + _error = response.Error(); + if ( (_error != 0) || (response.Data() == nullptr)) { + _value.clear(); + } + else { + _value = std::string(reinterpret_cast(response.Data()), response.Length()); + } + return(0); + } + + private: + uint16_t _handle; + uint8_t _rights; + uint16_t _end; + UUID _type; + std::list _descriptors; + uint8_t _error; + std::string _value; + }; + + public: + typedef Core::IteratorType< const std::list, const Characteristic&, std::list::const_iterator> Iterator; + typedef Core::IteratorType< std::list, Characteristic&> Index; + + Service(const Service&) = delete; + Service& operator= (const Service&) = delete; + + Service(const UUID& serviceId, const uint16_t handle, const uint16_t group) + : _handle(handle) + , _group(group) + , _serviceId(serviceId) + , _characteristics() { + } + ~Service() { + } + + public: + bool operator== (const UUID& id) const { + return (id == _serviceId); + } + bool operator!= (const UUID& id) const { + return (!operator== (id)); + } + const UUID& Type() const { + return (_serviceId); + } + string Name() const { + string result; + if (_serviceId.HasShort() == false) { + result = _serviceId.ToString(); + } + else { + Core::EnumerateType value(static_cast(_serviceId.Short())); + result = (value.IsSet() == true ? string(value.Data()) : _serviceId.ToString(false)); + } + return (result); + } + Iterator Characteristics() const { + return (Iterator(_characteristics)); + } + const Characteristic* operator[] (const UUID& id) const { + std::list::const_iterator index (_characteristics.begin()); + + while ((index != _characteristics.end()) && (*index != id)) { index++; } + + return (index != _characteristics.end() ? &(*index) : nullptr); + } + uint16_t Handle() const { + return (_handle); + } + uint16_t Max() const { + return (_group); + } + + private: + friend class GATTProfile; + void AddCharacteristics (GATTSocket::Command::Response& response) { + if (response.Next() == true) { + do { + uint16_t value (response.Group()); + uint8_t rights (response.Rights()); + UUID attribute (response.Attribute()); + + // Where does the next one start ? + uint16_t end = (response.Next() ? response.Handle() - 1 : Max()); + + _characteristics.emplace_back(end, rights, value, attribute); + + } while (response.IsValid() == true); + } + } + Index Filler() { + return (Index(_characteristics)); + } + + private: + uint16_t _handle; + uint16_t _group; + UUID _serviceId; + std::list _characteristics; + }; + + public: + typedef std::function Handler; + typedef Core::IteratorType< const std::list, const Service&, std::list::const_iterator> Iterator; + + GATTProfile (const GATTProfile&) = delete; + GATTProfile& operator= (const GATTProfile&) = delete; + + GATTProfile(const bool includeVendorCharacteristics) + : _adminLock() + , _services() + , _index() + , _custom(includeVendorCharacteristics) + , _socket(nullptr) + , _command() + , _handler() + , _expired(0) { + } + ~GATTProfile() { + } + + public: + uint32_t Discover(const uint32_t waitTime, GATTSocket& socket, const Handler& handler) { + uint32_t result = Core::ERROR_INPROGRESS; + + _adminLock.Lock(); + if (_socket == nullptr) { + result = Core::ERROR_NONE; + _socket = &socket; + _expired = Core::Time::Now().Add(waitTime).Ticks(); + _handler = handler; + _services.clear(); + _command.ReadByGroupType(0x0001, 0xFFFF, UUID(PRIMARY_SERVICE_UUID)); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnServices(cmd); }); + } + _adminLock.Unlock(); + + return(result); + } + void Abort () { + Report(Core::ERROR_ASYNC_ABORTED); + } + bool IsValid() const { + return ((_services.size() > 0) && (_expired == Core::ERROR_NONE)); + } + Iterator Services() const { + return (Iterator(_services)); + } + const Service* operator[] (const UUID& id) const { + std::list::const_iterator index (_services.begin()); + + while ((index != _services.end()) && (*index != id)) { index++; } + + return (index != _services.end() ? &(*index) : nullptr); + } + void Find(const UUID& serviceUuid, const UUID& charUuid, std::list& characteristics) const + { + const Service* service = (*this)[serviceUuid]; + if (service != nullptr) { + auto it = service->Characteristics(); + while (it.Next() == true) { + if (it.Current() == charUuid) { + characteristics.push_back(&it.Current()); + } + } + } + } + uint16_t FindHandle(const Service::Characteristic& characteristic, const UUID& descUuid) const + { + uint16_t handle = 0; + const Service::Characteristic::Descriptor* descriptor = characteristic[descUuid]; + if (descriptor != nullptr) { + handle = descriptor->Handle(); + } + return (handle); + } + uint16_t FindHandle(const UUID& serviceUuid, const UUID& charUuid) const + { + const Service::Characteristic* characteristic = FindCharacteristic(serviceUuid, charUuid); + return (characteristic == nullptr ? 0 : characteristic->Handle()); + } + uint16_t FindHandle(const UUID& serviceUuid, const UUID& charUuid, const UUID& descUuid) const + { + uint16_t handle = 0; + const Service::Characteristic* characteristic = FindCharacteristic(serviceUuid, charUuid); + if (characteristic != nullptr) { + const Service::Characteristic::Descriptor* descriptor = (*characteristic)[descUuid]; + if (descriptor != nullptr) { + handle = descriptor->Handle(); + } + } + return (handle); + } + + private: + const Service::Characteristic* FindCharacteristic(const UUID& serviceUuid, const UUID& charUuid) const + { + const Service::Characteristic* result = nullptr; + const Service* service = (*this)[serviceUuid]; + if (service != nullptr) { + result = (*service)[charUuid]; + } + return (result); + } + std::list::iterator ValidService(const std::list::iterator& input) { + std::list::iterator index (input); + while ( (index != _services.end()) && + ( (index->Handle() >= index->Max()) || ((_custom == false) && (index->Type().HasShort() == false)) ) ) { + index++; + } + + return (index); + } + bool NextCharacteristic() { + do { + if ( _characteristics.Next() == false) { + _index = ValidService(++_index); + + if (_index != _services.end()) { + _characteristics = _index->Filler(); + } + } + + } while ((_index != _services.end()) && (_characteristics.IsValid() == false)); + + return (_characteristics.IsValid()); + } + void LoadCharacteristics(uint32_t waitTime) { + uint16_t begin = _characteristics.Current().Handle(); + uint16_t end = _characteristics.Current().Max(); + + _adminLock.Lock(); + + if (_socket != nullptr) { + if (begin < end){ + _command.FindInformation(begin + 1, end); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnDescriptors(cmd); }); + } + else { + _command.Read(_characteristics.Current().Handle()); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnAttribute(cmd); }); + } + } + _adminLock.Unlock(); + } + void OnServices(const GATTSocket::Command& cmd) { + ASSERT (&cmd == &_command); + + if ( (cmd.Error() != Core::ERROR_NONE) && (cmd.Result().Error() != 0) ) { + // Seems like the services could not be discovered, report it.. + Report(Core::ERROR_GENERAL); + } + else { + uint32_t waitTime = AvailableTime(); + + if (waitTime > 0) { + GATTSocket::Command::Response& response(_command.Result()); + + while (response.Next() == true) { + const uint8_t* service = response.Data(); + if (response.Length() == 2) { + _services.emplace_back( UUID(service[0] | (service[1] << 8)), response.Handle(), response.Group() ); + } + else if (response.Length() == 16) { + _services.emplace_back( UUID(service), response.Handle(), response.Group() ); + } + } + + if (_services.size() == 0) { + Report (Core::ERROR_UNAVAILABLE); + } + else { + _index = ValidService(_services.begin()); + + if (_index == _services.end()) { + Report (Core::ERROR_NONE); + } + else { + _adminLock.Lock(); + if (_socket != nullptr) { + _command.ReadByType(_index->Handle()+1, _index->Max(), UUID(CHARACTERISTICS_UUID)); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnCharacteristics(cmd); }); + } + _adminLock.Unlock(); + } + } + } + } + } + void OnCharacteristics(const GATTSocket::Command& cmd) { + ASSERT (&cmd == &_command); + + if ( (cmd.Error() != Core::ERROR_NONE) && (cmd.Result().Error() != 0) ) { + // Seems like the services could not be discovered, report it.. + Report(Core::ERROR_GENERAL); + } + else { + uint32_t waitTime = AvailableTime(); + + if (waitTime > 0) { + GATTSocket::Command::Response& response(_command.Result()); + + _index->AddCharacteristics(response); + _index = ValidService(++_index); + + if (_index != _services.end()) { + _adminLock.Lock(); + if (_socket != nullptr) { + _command.ReadByType(_index->Handle()+1, _index->Max(), UUID(CHARACTERISTICS_UUID)); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnCharacteristics(cmd); }); + } + _adminLock.Unlock(); + } + else { + + // Time to start reading the attributes on the services!! + _index = _services.begin(); + + // If we get here, there must be services, otherwise we would have bailed out on OnServices!! + ASSERT (_index != _services.end()); + + _characteristics = _index->Filler(); + + if (NextCharacteristic() == false) { + Report(Core::ERROR_NONE); + } + else { + LoadCharacteristics(waitTime); + } + } + } + } + } + void OnDescriptors(const GATTSocket::Command& cmd) { + ASSERT (&cmd == &_command); + + if ( (cmd.Error() != Core::ERROR_NONE) && (cmd.Result().Error() != 0) ) { + // Seems like the services could not be discovered, report it.. + Report(Core::ERROR_GENERAL); + } + else { + uint32_t waitTime = AvailableTime(); + + if (waitTime > 0) { + _characteristics.Current().Descriptors(_command.Result()); + + _adminLock.Lock(); + + if (_socket != nullptr) { + + _command.Read(_characteristics.Current().Handle()); + _socket->Execute(waitTime, _command, [&](const GATTSocket::Command& cmd) { OnAttribute(cmd); }); + } + + _adminLock.Unlock(); + } + } + } + void OnAttribute(const GATTSocket::Command& cmd) { + ASSERT (&cmd == &_command); + + if (cmd.Error() != Core::ERROR_NONE) { + // Seems like the services could not be discovered, report it.. + Report(Core::ERROR_GENERAL); + } + else { + uint32_t waitTime = AvailableTime(); + + if (waitTime > 0) { + + _characteristics.Current().Value(_command.Result()); + + if (NextCharacteristic() == false) { + Report(Core::ERROR_NONE); + } + else { + LoadCharacteristics(waitTime); + } + } + } + } + void Report(const uint32_t result) { + _adminLock.Lock(); + if (_socket != nullptr) { + Handler caller = _handler; + _socket = nullptr; + _handler = nullptr; + _expired = result; + + caller(result); + } + _adminLock.Unlock(); + } + uint32_t AvailableTime () { + uint64_t now = Core::Time::Now().Ticks(); + uint32_t result = (now >= _expired ? 0 : static_cast((_expired - now) / Core::Time::TicksPerMillisecond)); + + if (result == 0) { + Report(Core::ERROR_TIMEDOUT); + } + return (result); + } + + private: + Core::CriticalSection _adminLock; + std::list _services; + std::list::iterator _index; + Service::Index _characteristics; + bool _custom; + GATTSocket* _socket; + GATTSocket::Command _command; + Handler _handler; + uint64_t _expired; + }; + +} // namespace Bluetooth + +} // namespace Thunder + diff --git a/Source/extensions/bluetooth/gatt/GATTSocket.cpp b/Source/extensions/bluetooth/gatt/GATTSocket.cpp new file mode 100644 index 000000000..b71ce7495 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/GATTSocket.cpp @@ -0,0 +1,356 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GATTSocket.h" + +namespace Thunder { + +namespace Bluetooth { + +uint16_t Attribute::Deserialize(const uint16_t size, const uint8_t stream[]) +{ + uint16_t result = 0; + + if (size > 0) { + if (_offset == 0) { + _type = stream[0]; + if ((_type & 0x7) <= 4) { + if (_type == 0) { + result = 1; + + // It's a nil, nothing more required + _offset = static_cast(~0); + _type = stream[0]; + } else { + uint8_t length = (1 << (_type & 0x07)); + uint8_t copyLength = std::min(length, static_cast(size - 1)); + ::memcpy(_buffer, &stream[1], copyLength); + result = 1 + copyLength; + _offset = (copyLength == length ? ~0 : copyLength); + } + } else { + uint8_t length = (1 << ((_type & 0x07) - 5)); + uint8_t copyLength = std::min(length, static_cast(size - 1)); + ::memcpy(_buffer, &stream[1], copyLength); + result = 1 + copyLength; + _offset = copyLength; + } + } + + if ((result < size) && (IsLoaded() == false)) { + // See if we need to set the length + if (((_type & 0x7) > 4) && (_length == static_cast(~0))) { + uint8_t length = (1 << ((_type & 0x07) - 5)); + uint8_t copyLength = std::min(static_cast(length - _offset), static_cast(size - result)); + if (copyLength > 0) { + ::memcpy(&(_buffer[_offset]), &stream[result], copyLength); + result += copyLength; + _offset += copyLength; + } + + if (_offset == length) { + _length = 0; + + for (uint8_t index = 0; index < length; index++) { + _length = (length << 8) | _buffer[index]; + } + _offset = (_length == 0 ? ~0 : 0); + } + } + + if (result < size) { + // Normal payload loading... + uint32_t copyLength = std::min(Length() - _offset, static_cast(size - result)); + // TODO: This one might potentially get very big. Check if the buffer still fits.. + ::memcpy(&(_buffer[_offset]), &stream[result], copyLength); + result += copyLength; + _offset = (_offset + copyLength == Length() ? ~0 : _offset + copyLength); + } + } + } + return (result); +} + +/* virtual */ uint16_t GATTSocket::Command::Deserialize(const uint8_t stream[], const uint16_t length) +{ + uint16_t result = 0; + + // See if we need to retrigger.. + if ((stream[0] != _id) && ((stream[0] != ATT_OP_ERROR) && (length >= 2) && (stream[1] == _id))) { + TRACE_L1(_T("Unexpected L2CapSocket message. Expected: %d, got %d [%d]"), _id, stream[0], stream[1]); + } else if (stream[0] != ATT_OP_HANDLE_NOTIFY) { + result = length; + + // TRACE_L1(_T("L2CapSocket Receive [%d], Type: %02X"), length, stream[0]); + // printf("L2CAP received [%d]: ", length); + // for (uint8_t index = 0; index < (length - 1); index++) { printf("%02X:", stream[index]); } printf("%02X\n", stream[length - 1]); + + // This is what we are expecting, so process it... + switch (stream[0]) { + case ATT_OP_ERROR: { + if ((stream[4] == ATT_ECODE_ATTR_NOT_FOUND) && (_frame.End() != 0) && (_response.Empty() == false)) { + _error = Core::ERROR_NONE; + } + else { + _response._min = stream[4]; + _response.Type(stream[0]); + _error = Core::ERROR_GENERAL; + } + break; + } + case ATT_OP_READ_BY_GROUP_RESP: { + /* PDU must contain at least: + * - Attribute length (1 octet) + * - Attribute Data List (at least one entry): + * - Attribute Handle (2 octets) + * - End Group Handle (2 octets) + * - Data (Attribute length - 4) */ + /* Minimum Attribute Data List size */ + if (stream[1] < 4) { + _error = Core::ERROR_BAD_REQUEST; + } + else { + uint16_t last = 0; + uint8_t entries = (length - 2) / stream[1]; + for (uint8_t index = 0; index < entries; index++) { + uint16_t offset = 2 + (index * stream[1]); + uint16_t foundHandle = (stream[offset + 1] << 8) | stream[offset + 0]; + uint16_t groupHandle = (stream[offset + 3] << 8) | stream[offset + 2]; + _response.Add(foundHandle, groupHandle, (stream[1] - 4), &(stream[offset+4])); + if (last < groupHandle) { + last = groupHandle; + } + } + + ASSERT(_frame.End() != 0); + + if (last >= _frame.End()) { + _error = Core::ERROR_NONE; + } + else { + _frame.ReadByGroupType(last + 1); + } + + _response.Type(stream[0]); + } + break; + } + case ATT_OP_FIND_BY_TYPE_RESP: { + /* PDU must contain at least: + * - Attribute Opcode (1 octet) + * - Length (1 octet) + * - Attribute Data List (at least one entry): + * - Attribute Handle (2 octets) + * - Attribute Value (at least 1 octet) */ + /* Minimum Attribute Data List size */ + if (length < 5) { + _error = Core::ERROR_BAD_REQUEST; + } + else { + uint16_t last = 0; + uint8_t entries = (length - 1) / 4; + for (uint8_t index = 0; index < entries; index++) { + uint16_t offset = 1 + (index * 4); + uint16_t foundHandle = (stream[offset + 1] << 8) | stream[offset + 0]; + uint16_t groupHandle = (stream[offset + 3] << 8) | stream[offset + 2]; + _response.Add(foundHandle, groupHandle); + + if (last < groupHandle) { + last = groupHandle; + } + } + + ASSERT(_frame.End() != 0); + + if (last >= _frame.End()) { + _error = Core::ERROR_NONE; + } + else { + _frame.FindByType(last + 1); + } + + _response.Type(stream[0]); + } + + break; + } + case ATT_OP_READ_BY_TYPE_RESP: { + /* PDU must contain at least: + * - Attribute Opcode (1 octet) + * - Length (1 octet) + * - Attribute Data List (at least one entry): + * - Attribute Handle (2 octets) + * - Attribute Value (at least 1 octet) */ + /* Minimum Attribute Data List size */ + if (stream[1] < 3) { + _error = Core::ERROR_BAD_REQUEST; + } + else { + uint16_t last = 0; + uint8_t entries = ((length - 2) / stream[1]); + for (uint8_t index = 0; index < entries; index++) { + uint16_t offset = 2 + (index * stream[1]); + uint16_t handle = (stream[offset + 1] << 8) | stream[offset + 0]; + + _response.Add(handle, stream[1] - 2, &(stream[offset + 2])); + if (last < handle) { + last = handle; + } + } + + ASSERT(_frame.End() != 0); + + if (last >= _frame.End()) { + _error = Core::ERROR_NONE; + } + else { + _frame.ReadByType(last + 1); + } + + _response.Type(stream[0]); + } + + break; + } + case ATT_OP_FIND_INFO_RESP: { + if ((stream[1] != 1) && (stream[1] != 2)) { + _error = Core::ERROR_BAD_REQUEST; + } + else { + uint16_t last = 0; + uint8_t step = (stream[1] == 1 ? 2 : 16); + uint8_t entries = ((length - 2) / (2 + step)); + + for (uint8_t index = 0; index < entries; index++) { + uint16_t offset = 2 + (index * (2 + step)); + uint16_t handle = (stream[offset + 1] << 8) | stream[offset + 0]; + + _response.Add(handle, step, &(stream[offset + 2])); + if (last < handle) { + last = handle; + } + } + + ASSERT(_frame.End() != 0); + + if (last >= _frame.End()) { + _error = Core::ERROR_NONE; + } + else { + _frame.FindInformation(last + 1); + } + + _response.Type(stream[0]); + } + + break; + } + case ATT_OP_WRITE_RESP: { + _error = Core::ERROR_NONE; + _response.Type(stream[0]); + break; + } + case ATT_OP_READ_RESP: { + _response.Add(_frame.Handle(), length - 1, &(stream[1])); + if (length == _mtu) { + _id = _frame.ReadBlob(_frame.Handle(), _response.Offset()); + _frame.Reload(); + } + else { + _error = Core::ERROR_NONE; + } + + _response.Type(stream[0]); + break; + } + case ATT_OP_READ_BLOB_RESP: { + if (_response.Offset() == 0) { + _response.Add(_frame.Handle(), length - 1, &(stream[1])); + } else { + _response.Extend(length - 1, &(stream[1])); + } + if (length == _mtu) { + _id = _frame.ReadBlob(_frame.Handle(), _response.Offset()); + _frame.Reload(); + } else { + _error = Core::ERROR_NONE; + } + _response.Type(ATT_OP_READ_RESP); + break; + } + default: + break; + } + } + return (result); +} + +bool GATTSocket::Security(const uint8_t level) +{ + bool result = true; + + int lm = 0; + switch (level) { + case BT_SECURITY_SDP: + break; + case BT_SECURITY_LOW: + lm = L2CAP_LM_AUTH; + break; + case BT_SECURITY_MEDIUM: + lm = L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT; + break; + case BT_SECURITY_HIGH: + lm = L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE; + break; + default: + TRACE_L1("Invalid security level"); + result = false; + } + + if (result == true) { + struct bt_security btSecurity; + btSecurity.level = level; + btSecurity.key_size = 0; + if (::setsockopt(Handle(), SOL_BLUETOOTH, BT_SECURITY, &btSecurity, sizeof(btSecurity)) != 0) { + TRACE_L1("Failed to set Bluetooth Security level for device [%s], error: %d, try L2CAP Security", RemoteId().c_str(), errno); + if (::setsockopt(Handle(), SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)) != 0) { + TRACE_L1("Error setting L2CAP Security for device [%s], error: %d", RemoteId().c_str(), errno); + result = false; + } + } + } + + return (result); +} + +/* virtual */ void GATTSocket::StateChange() +{ + Core::SynchronousChannelType::StateChange(); + + if (IsOpen() == true) { + socklen_t len = sizeof(_connectionInfo); + ::getsockopt(Handle(), SOL_L2CAP, L2CAP_CONNINFO, &_connectionInfo, &len); + + Send(CommunicationTimeOut, _sink, &_sink, &_sink); + } +} + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/gatt/GATTSocket.h b/Source/extensions/bluetooth/gatt/GATTSocket.h new file mode 100644 index 000000000..9cad93781 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/GATTSocket.h @@ -0,0 +1,908 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" + +namespace Thunder { + +namespace Bluetooth { + + class EXTERNAL Attribute { + public: + enum type { + NIL = 0x00, + INTEGER_UNSIGNED = 0x08, + INTEGER_SIGNED = 0x10, + UUID = 0x18, + TEXT = 0x20, + BOOLEAN = 0x28, + SEQUENCE = 0x30, + ALTERNATIVE = 0x38, + URL = 0x40 + }; + + Attribute() + : _type(~0) + , _offset(0) + , _length(~0) + , _bufferSize(64) + , _buffer(reinterpret_cast(::malloc(_bufferSize))) + { + } + Attribute(const uint16_t size, const uint8_t stream[]) + : _type(~0) + , _offset(0) + , _length(~0) + , _bufferSize(size) + , _buffer(reinterpret_cast(::malloc(_bufferSize))) { + Deserialize(size, stream); + } + ~Attribute() + { + if (_buffer != nullptr) { + ::free(_buffer); + } + } + + public: + void Clear() + { + _type = ~0; + _offset = 0; + _length = ~0; + } + bool IsLoaded() const + { + return (_offset == static_cast(~0)); + } + type Type() const + { + return (static_cast(_type & 0xF8)); + } + uint32_t Length() const + { + return ((_type & 0x3) <= 4 ? (_type == 0 ? 0 : 1 << (_type & 0x03)) : _length); + } + template + void Load(TYPE& value) const + { + value = 0; + for (uint8_t index = 0; index < sizeof(TYPE); index++) { + value = (value << 8) | _buffer[index]; + } + } + void Load(bool& value) const + { + value = (_buffer[0] != 0); + } + void Load(string& value) const + { + value = string(reinterpret_cast(_buffer), _length); + } + + uint16_t Deserialize(const uint16_t size, const uint8_t stream[]); + + private: + uint8_t _type; + uint32_t _offset; + uint32_t _length; + uint32_t _bufferSize; + uint8_t* _buffer; + }; + + class EXTERNAL GATTSocket : public Core::SynchronousChannelType { + public: + static constexpr uint8_t LE_ATT_CID = 4; + + enum rights : uint8_t { + Broadcast = 0x01, + Read = 0x02, + Write = 0x04, + WriteResponse = 0x08, + Notify = 0x10, + Indicate = 0x20 + }; + + private: + GATTSocket(const GATTSocket&) = delete; + GATTSocket& operator=(const GATTSocket&) = delete; + + static constexpr uint8_t ATT_OP_ERROR = 0x01; + static constexpr uint8_t ATT_OP_MTU_REQ = 0x02; + static constexpr uint8_t ATT_OP_MTU_RESP = 0x03; + static constexpr uint8_t ATT_OP_FIND_INFO_REQ = 0x04; + static constexpr uint8_t ATT_OP_FIND_INFO_RESP = 0x05; + static constexpr uint8_t ATT_OP_FIND_BY_TYPE_REQ = 0x06; + static constexpr uint8_t ATT_OP_FIND_BY_TYPE_RESP = 0x07; + static constexpr uint8_t ATT_OP_READ_BY_TYPE_REQ = 0x08; + static constexpr uint8_t ATT_OP_READ_BY_TYPE_RESP = 0x09; + static constexpr uint8_t ATT_OP_READ_REQ = 0x0A; + static constexpr uint8_t ATT_OP_READ_RESP = 0x0B; + static constexpr uint8_t ATT_OP_READ_BLOB_REQ = 0x0C; + static constexpr uint8_t ATT_OP_READ_BLOB_RESP = 0x0D; + static constexpr uint8_t ATT_OP_READ_MULTI_REQ = 0x0E; + static constexpr uint8_t ATT_OP_READ_MULTI_RESP = 0x0F; + static constexpr uint8_t ATT_OP_READ_BY_GROUP_REQ = 0x10; + static constexpr uint8_t ATT_OP_READ_BY_GROUP_RESP = 0x11; + static constexpr uint8_t ATT_OP_WRITE_REQ = 0x12; + static constexpr uint8_t ATT_OP_WRITE_CMD = 0x52; + static constexpr uint8_t ATT_OP_WRITE_RESP = 0x13; + static constexpr uint8_t ATT_OP_HANDLE_NOTIFY = 0x1B; + + + static constexpr uint8_t ATT_ECODE_INVALID_HANDLE = 0x01; + static constexpr uint8_t ATT_ECODE_READ_NOT_PERM = 0x02; + static constexpr uint8_t ATT_ECODE_WRITE_NOT_PERM = 0x03; + static constexpr uint8_t ATT_ECODE_INVALID_PDU = 0x04; + static constexpr uint8_t ATT_ECODE_AUTHENTICATION = 0x05; + static constexpr uint8_t ATT_ECODE_REQ_NOT_SUPP = 0x06; + static constexpr uint8_t ATT_ECODE_INVALID_OFFSET = 0x07; + static constexpr uint8_t ATT_ECODE_AUTHORIZATION = 0x08; + static constexpr uint8_t ATT_ECODE_PREP_QUEUE_FULL = 0x09; + static constexpr uint8_t ATT_ECODE_ATTR_NOT_FOUND = 0x0A; + static constexpr uint8_t ATT_ECODE_ATTR_NOT_LONG = 0x0B; + static constexpr uint8_t ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0C; + static constexpr uint8_t ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0D; + static constexpr uint8_t ATT_ECODE_UNLIKELY = 0x0E; + static constexpr uint8_t ATT_ECODE_INSUFF_ENC = 0x0F; + static constexpr uint8_t ATT_ECODE_UNSUPP_GRP_TYPE = 0x10; + static constexpr uint8_t ATT_ECODE_INSUFF_RESOURCES = 0x11; + + /* Application error */ + static constexpr uint8_t ATT_ECODE_IO = 0x80; + static constexpr uint8_t ATT_ECODE_TIMEOUT = 0x81; + static constexpr uint8_t ATT_ECODE_ABORTED = 0x82; + + class CommandSink : public Core::IOutbound::ICallback, public Core::IOutbound, public Core::IInbound + { + public: + CommandSink() = delete; + CommandSink(const CommandSink&) = delete; + CommandSink& operator= (const CommandSink&) = delete; + + CommandSink(GATTSocket& parent, const uint16_t preferredMTU) : _parent(parent), _mtu(preferredMTU) { + Reload(); + } + virtual ~CommandSink() { + } + + public: + inline uint16_t MTU () const { + return (_mtu & 0xFFFF); + } + inline bool HasMTU() const { + return (_mtu <= 0xFFFF); + } + virtual void Updated(const Core::IOutbound& data, const uint32_t error_code) override + { + _parent.Completed(data, error_code); + } + + virtual void Reload() const override + { + _mtu = ((_mtu & 0xFFFF) | 0xFF000000); + } + virtual uint16_t Serialize(uint8_t stream[], const uint16_t length VARIABLE_IS_NOT_USED) const override + { + uint16_t result = 0; + if ((_mtu >> 24) == 0xFF) { + ASSERT(length >= 3); + stream[0] = ATT_OP_MTU_REQ; + stream[1] = (_mtu & 0xFF); + stream[2] = ((_mtu >> 8) & 0xFF); + _mtu = ((_mtu & 0xFFFF) | 0xF0000000); + result = 3; + } + return (result); + } + virtual Core::IInbound::state IsCompleted() const override + { + return ((_mtu >> 24) == 0x00 ? Core::IInbound::COMPLETED : Core::IInbound::INPROGRESS); + } + virtual uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override { + uint16_t result = 0; + + // See if we need to retrigger.. + if (stream[0] == ATT_OP_MTU_RESP) { + _mtu = ((stream[2] << 8) | stream[1]); + result = length; + } else if ((stream[0] == ATT_OP_ERROR) && (stream[1] == ATT_OP_MTU_RESP)) { + TRACE_L1("Error on receiving MTU: [%d]", stream[2]); + result = length; + } else { + TRACE_L1("Unexpected L2CapSocket message. Expected: %d, got %d [%d]", ATT_OP_MTU_RESP, stream[0], stream[1]); + result = 0; + } + return (result); + } + + private: + GATTSocket& _parent; + mutable uint32_t _mtu; + }; + + public: + static constexpr uint32_t CommunicationTimeOut = 2000; /* 2 seconds. */ + + class EXTERNAL Command : public Core::IOutbound, public Core::IInbound { + private: + Command(const Command&) = delete; + Command& operator=(const Command&) = delete; + + static constexpr uint16_t BLOCKSIZE = 64; + + class Exchange { + private: + Exchange(const Exchange&) = delete; + Exchange& operator=(const Exchange&) = delete; + + public: + Exchange() + : _offset(~0) + , _size(0) + { + } + ~Exchange() + { + } + + public: + bool IsSend() const + { + return (_offset >= _size); + } + void Reload() const + { + _offset = 0; + } + void ReadByGroupType(const uint16_t start) + { + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + Reload(); + } + uint8_t ReadByGroupType(const uint16_t start, const uint16_t end, const UUID& id) + { + _buffer[0] = ATT_OP_READ_BY_GROUP_REQ; + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + _buffer[3] = (end & 0xFF); + _buffer[4] = (end >> 8) & 0xFF; + ::memcpy(&(_buffer[5]), id.Data(), id.Length()); + _size = id.Length() + 5; + _end = end; + return (ATT_OP_READ_BY_GROUP_RESP); + } + void FindByType(const uint16_t start) + { + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + Reload(); + } + uint8_t FindByType(const uint16_t start, const uint16_t end, const UUID& id, const uint8_t length, const uint8_t data[]) + { + _buffer[0] = ATT_OP_FIND_BY_TYPE_REQ; + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + _buffer[3] = (end & 0xFF); + _buffer[4] = (end >> 8) & 0xFF; + _buffer[5] = (id.Short() & 0xFF); + _buffer[6] = (id.Short() >> 8) & 0xFF; + ::memcpy(&(_buffer[7]), data, length); + _size = 7 + length; + _end = end; + return (ATT_OP_FIND_BY_TYPE_RESP); + } + uint8_t FindByType(const uint16_t start, const uint16_t end, const UUID& id, const uint16_t handle) + { + _buffer[0] = ATT_OP_FIND_BY_TYPE_REQ; + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + _buffer[3] = (end & 0xFF); + _buffer[4] = (end >> 8) & 0xFF; + _buffer[5] = (id.Short() & 0xFF); + _buffer[6] = (id.Short() >> 8) & 0xFF; + _buffer[7] = (handle & 0xFF); + _buffer[8] = (handle >> 8) & 0xFF; + _size = 9; + _end = end; + return (ATT_OP_FIND_BY_TYPE_RESP); + } + void ReadByType(const uint16_t start) + { + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + Reload(); + } + uint8_t ReadByType(const uint16_t start, const uint16_t end, const UUID& id) + { + _buffer[0] = ATT_OP_READ_BY_TYPE_REQ; + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + _buffer[3] = (end & 0xFF); + _buffer[4] = (end >> 8) & 0xFF; + ::memcpy(&(_buffer[5]), id.Data(), id.Length()); + _size = id.Length() + 5; + _end = end; + return (ATT_OP_READ_BY_TYPE_RESP); + } + void FindInformation(const uint16_t start) + { + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + Reload(); + } + uint8_t FindInformation(const uint16_t start, const uint16_t end) + { + _buffer[0] = ATT_OP_FIND_INFO_REQ; + _buffer[1] = (start & 0xFF); + _buffer[2] = (start >> 8) & 0xFF; + _buffer[3] = (end & 0xFF); + _buffer[4] = (end >> 8) & 0xFF; + _size = 5; + _end = end; + return (ATT_OP_FIND_INFO_RESP); + } + uint8_t Read(const uint16_t handle) + { + _buffer[0] = ATT_OP_READ_REQ; + _buffer[1] = (handle & 0xFF); + _buffer[2] = (handle >> 8) & 0xFF; + _size = 3; + _end = 0; + return (ATT_OP_READ_RESP); + } + uint8_t ReadBlob(const uint16_t handle, const uint16_t offset) + { + _buffer[0] = ATT_OP_READ_BLOB_REQ; + _buffer[1] = (handle & 0xFF); + _buffer[2] = (handle >> 8) & 0xFF; + _buffer[3] = (offset & 0xFF); + _buffer[4] = (offset >> 8) & 0xFF; + _size = 5; + _end = 0; + return (ATT_OP_READ_BLOB_RESP); + } + void WriteCommand(const uint16_t handle, const uint8_t length, const uint8_t data[]) + { + _buffer[0] = ATT_OP_WRITE_CMD; + _buffer[1] = (handle & 0xFF); + _buffer[2] = (handle >> 8) & 0xFF; + ::memcpy(&(_buffer[3]), data, length); + _size = 3 + length; + _end = 0; + } + uint8_t Write(const uint16_t handle, const uint8_t length, const uint8_t data[]) + { + _buffer[0] = ATT_OP_WRITE_REQ; + _buffer[1] = (handle & 0xFF); + _buffer[2] = (handle >> 8) & 0xFF; + ::memcpy(&(_buffer[3]), data, length); + _size = 3 + length; + _end = 0; + return (ATT_OP_WRITE_RESP); + } + uint16_t Serialize(uint8_t stream[], const uint16_t length) const + { + uint16_t result = std::min(static_cast(_size - _offset), length); + if (result > 0) { + ::memcpy(stream, &(_buffer[_offset]), result); + _offset += result; + + // printf("L2CAP send [%d]: ", result); + // for (uint8_t index = 0; index < (result - 1); index++) { printf("%02X:", stream[index]); } printf("%02X\n", stream[result - 1]); + } + + return (result); + } + uint16_t Handle() const + { + return (((_buffer[0] == ATT_OP_READ_BLOB_REQ) || (_buffer[0] == ATT_OP_READ_REQ)) ? ((_buffer[2] << 8) | _buffer[1]) : 0); + } + uint16_t Offset() const + { + return ((_buffer[0] == ATT_OP_READ_BLOB_REQ) ? ((_buffer[4] << 8) | _buffer[3]) : 0); + } + uint16_t End() const { + return (_end); + } + + private: + mutable uint16_t _offset; + uint16_t _end; + uint8_t _size; + uint8_t _buffer[BLOCKSIZE]; + }; + + public: + class EXTERNAL Response { + private: + Response(const Response&) = delete; + Response& operator=(const Response&) = delete; + + typedef std::pair > Entry; + + public: + Response() + : _maxSize(BLOCKSIZE) + , _loaded(0) + , _result() + , _iterator() + , _storage(reinterpret_cast(::malloc(_maxSize))) + , _preHead(true) + , _min(0x0001) + , _max(0xFFFF) + , _type(ATT_OP_ERROR) + { + } + ~Response() + { + if (_storage != nullptr) { + ::free(_storage); + } + } + + public: + uint8_t Type() const { + return(_type); + } + void Dump () const { + if (_storage != nullptr) { + fprintf (stderr, "Loaded data [%d]:", _loaded); + for (uint16_t index = 0; index < _loaded; index++) { + if (index == 0) { + fprintf (stderr, " %02X", _storage[index]); + } + else { + fprintf (stderr, ":%02X", _storage[index]); + } + } + fprintf (stderr, "\n"); + } + else { + fprintf (stderr, "There is no data to print..\n"); + } + } + void Clear() + { + Reset(); + _result.clear(); + _loaded = 0; + _min = 0xFFFF; + _max = 0x0001; + _type = ATT_OP_ERROR; + } + uint8_t Error() const { + return (_type == ATT_OP_ERROR ? static_cast(_min) : 0); + } + void Reset() + { + _preHead = true; + } + bool IsValid() const + { + return ((_preHead == false) && (_iterator != _result.end())); + } + bool Next() + { + if (_preHead == true) { + _preHead = false; + _iterator = _result.begin(); + } else { + _iterator++; + } + return (_iterator != _result.end()); + } + uint16_t Count() const + { + return (_result.size()); + } + bool Empty() const + { + return (_result.empty()); + } + uint16_t Handle() const + { + return (IsValid() == true ? _iterator->first : (_result.size() == 1 ? _result.begin()->first : 0)); + } + uint16_t MTU() const + { + return ((_storage[0] << 8) | _storage[1]); + } + uint16_t Group() const + { + return (_type == ATT_OP_READ_BY_TYPE_RESP ? ((_storage[_iterator->second.second + 2] << 8) | (_storage[_iterator->second.second + 1])) : _iterator->second.first); + } + UUID Attribute() const { + uint8_t offset = (_type == ATT_OP_READ_BY_TYPE_RESP ? 3 : 0); + uint16_t length = Delta() - offset; + const uint8_t* data = &(_storage[_iterator->second.second + offset]); + + if ((length != 2) && (length != 16)) { + TRACE_L1("**** Unexpected Attribute length [%d] !!!!", length); + } + + return (length == 2 ? UUID(static_cast((data[1] << 8) | (*data))) : + length == 16 ? UUID(data) : + UUID()); + } + uint8_t Rights() const { + return (_storage[_iterator->second.second]); + } + uint16_t Length() const + { + uint16_t result = _loaded; + if (IsValid() == true) { + result = Delta(); + } + return (result); + } + const uint8_t* Data() const + { + return (IsValid() == true ? &(_storage[_iterator->second.second]) : (((_result.size() <= 1) && (_loaded > 0)) ? _storage : nullptr)); + } + uint16_t Min() const { + return(_min); + } + uint16_t Max() const { + return(_max); + } + + private: + friend class Command; + void Type(const uint8_t response) { + _type = response; + } + uint16_t Delta() const { + std::list::iterator next(_iterator); + return (++next == _result.end() ? (_loaded - _iterator->second.second) : (next->second.second - _iterator->second.second)); + } + void SetMTU(const uint16_t MTU) + { + _storage[0] = (MTU >> 8) & 0xFF; + _storage[1] = MTU & 0xFF; + } + void Add(const uint16_t handle, const uint16_t group) + { + if (_min > handle) + _min = handle; + if (_max < group) + _max = group; + _result.emplace_back(Entry(handle, std::pair(group,_loaded))); + } + void Add(const uint16_t handle, const uint8_t length, const uint8_t buffer[]) + { + if (_min > handle) + _min = handle; + if (_max < handle) + _max = handle; + + _result.emplace_back(Entry(handle, std::pair(0,_loaded))); + Extend(length, buffer); + } + void Add(const uint16_t handle, const uint16_t group, const uint8_t length, const uint8_t buffer[]) + { + if (_min > handle) + _min = handle; + if (_max < group) + _max = group; + _result.emplace_back(Entry(handle, std::pair(group,_loaded))); + Extend(length, buffer); + } + void Extend(const uint8_t length, const uint8_t buffer[]) + { + if (length > 0) { + if ((_loaded + length) > _maxSize) { + _maxSize = ((((_loaded + length) / BLOCKSIZE) + 1) * BLOCKSIZE); + _storage = reinterpret_cast(::realloc(_storage, _maxSize)); + } + + ::memcpy(&(_storage[_loaded]), buffer, length); + _loaded += length; + } + } + uint16_t Offset() const + { + return (_result.size() != 0 ? _loaded : _loaded - _result.back().second.second); + } + + private: + uint16_t _maxSize; + uint16_t _loaded; + std::list _result; + std::list::iterator _iterator; + uint8_t* _storage; + bool _preHead; + uint16_t _min; + uint16_t _max; + uint8_t _type; + }; + + public: + Command() + : _error(~0) + , _mtu(0) + , _id(0) + , _frame() + , _response() + { + } + virtual ~Command() + { + } + + public: + Response& Result() { + return (_response); + } + const Response& Result() const { + return (_response); + } + uint8_t Id() const { + return(_id); + } + uint16_t Error() const { + return(_error); + } + void FindInformation(const uint16_t min, const uint16_t max) + { + _response.Clear(); + _error = ~0; + _id = _frame.FindInformation(min, max); + } + void ReadByGroupType(const uint16_t min, const uint16_t max, const UUID& uuid) + { + _response.Clear(); + _error = ~0; + _id = _frame.ReadByGroupType(min, max, uuid); + } + void ReadByType(const uint16_t min, const uint16_t max, const UUID& uuid) + { + _response.Clear(); + _error = ~0; + _id = _frame.ReadByType(min, max, uuid); + } + void ReadBlob(const uint16_t handle) + { + _response.Clear(); + _error = ~0; + _id = _frame.ReadBlob(handle, 0); + } + void Read(const uint16_t handle) + { + _response.Clear(); + _error = ~0; + _id = _frame.Read(handle); + } + void WriteCommand(const uint16_t handle, const uint8_t length, const uint8_t data[]) + { + _response.Clear(); + _error = Core::ERROR_NONE; + _frame.WriteCommand(handle, length, data); + } + void Write(const uint16_t handle, const uint8_t length, const uint8_t data[]) + { + _response.Clear(); + _response.Extend(length, data); + _error = ~0; + _id = _frame.Write(handle, length, data); + } + void FindByType(const uint16_t min, const uint16_t max, const UUID& uuid, const uint8_t length, const uint8_t data[]) + { + ASSERT(uuid.HasShort() == true); + _response.Clear(); + _error = ~0; + _id = _frame.FindByType(min, max, uuid, length, data); + } + void FindByType(const uint16_t min, const uint16_t max, const UUID& uuid, const uint16_t handle) + { + ASSERT(uuid.HasShort() == true); + _response.Clear(); + _error = ~0; + _id = _frame.FindByType(min, max, uuid, handle); + } + + void Error(const uint32_t error_code) { + _error = error_code; + } + void MTU(const uint16_t mtu) { + _mtu = mtu; + } + + private: + virtual uint16_t Deserialize(const uint8_t stream[], const uint16_t length) override; + virtual void Reload() const override + { + _frame.Reload(); + } + virtual uint16_t Serialize(uint8_t stream[], const uint16_t length) const override + { + return (_frame.Serialize(stream, length)); + } + virtual Core::IInbound::state IsCompleted() const override + { + return (_error != static_cast(~0) ? Core::IInbound::COMPLETED : (_frame.IsSend() ? Core::IInbound::INPROGRESS : Core::IInbound::RESEND)); + } + + private: + uint16_t _error; + uint16_t _mtu; + uint8_t _id; + Exchange _frame; + Response _response; + }; + + private: + typedef std::function Handler; + + class Entry { + public: + Entry() = delete; + Entry(const Entry&) = delete; + Entry& operator= (const Entry&) = delete; + Entry(const uint32_t waitTime, Command& cmd, const Handler& handler) + : _waitTime(waitTime) + , _cmd(cmd) + , _handler(handler) { + } + ~Entry() { + } + + public: + Command& Cmd() { + return(_cmd); + } + uint32_t WaitTime() const { + return(_waitTime); + } + bool operator== (const Core::IOutbound* rhs) const { + return (rhs == &_cmd); + } + bool operator!= (const Core::IOutbound* rhs) const { + return(!operator==(rhs)); + } + void Completed(const uint32_t error_code) { + _cmd.Error(error_code); + _handler(_cmd); + } + + private: + uint32_t _waitTime; + Command& _cmd; + Handler _handler; + }; + + public: + GATTSocket(const Core::NodeId& localNode, const Core::NodeId& remoteNode, const uint16_t maxMTU) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, localNode, remoteNode, static_cast(48*1024), static_cast(48*1024)) + , _adminLock() + , _sink(*this, maxMTU) + , _queue() + { + } + GATTSocket(const Core::NodeId& localNode, const Core::NodeId& remoteNode, const uint16_t maxMTU, const uint16_t sendBufferSize, const uint16_t recvBufferSize) + : Core::SynchronousChannelType(SocketPort::SEQUENCED, localNode, remoteNode, sendBufferSize, recvBufferSize) + , _adminLock() + , _sink(*this, maxMTU) + , _queue() + { + } + virtual ~GATTSocket() + { + } + + public: + bool Security(const uint8_t level); + + inline uint16_t MTU() const { + return (_sink.MTU()); + } + uint32_t Execute(const uint32_t waitTime, Command& cmd) + { + cmd.MTU(_sink.MTU()); + return (Exchange(waitTime, cmd, cmd)); + } + void Execute(const uint32_t waitTime, Command& cmd, const Handler& handler) + { + cmd.MTU(_sink.MTU()); + _adminLock.Lock(); + _queue.emplace_back(waitTime, cmd, handler); + if (_queue.size() == 1) { + Send(waitTime, cmd, &_sink, &cmd); + } + _adminLock.Unlock(); + } + void Execute(Command& cmd) + { + cmd.MTU(_sink.MTU()); + Exchange(CommunicationTimeOut, cmd); + } + void Revoke(const Command& cmd) + { + Revoke(cmd); + } + + private: + virtual void Notification(const uint16_t handle, const uint8_t[], const uint16_t) = 0; + virtual void Operational() = 0; + + void StateChange() override; + + uint16_t Deserialize(const uint8_t dataFrame[], const uint16_t availableData) override { + uint32_t result = 0; + + if (availableData >= 1) { + const uint8_t& opcode = dataFrame[0]; + + if ((opcode == ATT_OP_HANDLE_NOTIFY) && (availableData >= 3)) { + uint16_t handle = ((dataFrame[2] << 8) | dataFrame[1]); + Notification(handle, &dataFrame[3], (availableData - 3)); + result = availableData; + } + else { + TRACE_L1("**** Unexpected data, TYPE [%02X] !!!!\n", dataFrame[0]); + } + } + else { + TRACE_L1("**** Unexpected data for deserialization [%d] !!!!", availableData); + } + + return (result); + } + void Completed(const Core::IOutbound& data, const uint32_t error_code) + { + if ( (&data == &_sink) && (_sink.HasMTU() == true) ) { + Operational(); + } + else { + _adminLock.Lock(); + + if ((_queue.size() == 0) || (*(_queue.begin()) != &data)) { + ASSERT (false && _T("Always the first one should be the one to be handled!!")); + } + else { + // Command completion... + _queue.begin()->Completed(error_code); + _queue.erase(_queue.begin()); + + if (_queue.size() > 0) { + Entry& entry(*(_queue.begin())); + Command& cmd (entry.Cmd()); + + Send(entry.WaitTime(), cmd, &_sink, &cmd); + } + } + + _adminLock.Unlock(); + } + } + + private: + Core::CriticalSection _adminLock; + CommandSink _sink; + std::list _queue; + uint32_t _mtuSize; + struct l2cap_conninfo _connectionInfo; + }; + +} // namespace Bluetooth + +} // namespace Thunder diff --git a/Source/extensions/bluetooth/gatt/Module.cpp b/Source/extensions/bluetooth/gatt/Module.cpp new file mode 100644 index 000000000..393d6a267 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/Module.cpp @@ -0,0 +1,22 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Source/extensions/bluetooth/gatt/Module.h b/Source/extensions/bluetooth/gatt/Module.h new file mode 100644 index 000000000..a3168d40c --- /dev/null +++ b/Source/extensions/bluetooth/gatt/Module.h @@ -0,0 +1,37 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME Bluetooth_GATT +#endif + +#include +#include + +#include <../include/bluetooth/bluetooth.h> + +#include "../Debug.h" +#include "../UUID.h" + +#if defined(__WINDOWS__) && defined(BLUETOOTH_EXPORTS) +#undef EXTERNAL +#define EXTERNAL EXTERNAL_EXPORT +#endif diff --git a/Source/extensions/bluetooth/gatt/bluetooth_gatt.h b/Source/extensions/bluetooth/gatt/bluetooth_gatt.h new file mode 100644 index 000000000..629d37567 --- /dev/null +++ b/Source/extensions/bluetooth/gatt/bluetooth_gatt.h @@ -0,0 +1,32 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#error "Please define a MODULE_NAME that describes the binary/library you are building." +#endif + +#include +#include "GATTSocket.h" +#include "GATTProfile.h" + +#ifdef __WINDOWS__ +#pragma comment(lib, "bluetoothgatt.lib") +#endif \ No newline at end of file diff --git a/Source/extensions/broadcast/CMakeLists.txt b/Source/extensions/broadcast/CMakeLists.txt new file mode 100644 index 000000000..57b5648f2 --- /dev/null +++ b/Source/extensions/broadcast/CMakeLists.txt @@ -0,0 +1,129 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) + +project(${NAMESPACE}Broadcast + VERSION 1.0.0 + DESCRIPTION "Abstraction to parse DVB tables" + LANGUAGES CXX) + +set(TARGET ${PROJECT_NAME}) +message("Setup ${TARGET} v${PROJECT_VERSION}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +find_package(NXCLIENT QUIET) + +add_library(${TARGET} + ProgramTable.cpp + Definitions.cpp + TunerAdministrator.cpp + Module.cpp + ) + +set(PUBLIC_HEADERS + broadcast.h + Definitions.h + Descriptors.h + MPEGDescriptor.h + MPEGSection.h + MPEGTable.h + ProgramTable.h + TunerAdministrator.h + Services.h + Networks.h + TimeDate.h + Schedule.h + NIT.h + SDT.h + TDT.h + EIT.h + Module.h + ) + +target_link_libraries(${TARGET} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Core::${NAMESPACE}Core + ) + +if(NXCLIENT_FOUND) + find_package(NEXUS REQUIRED) + + if (BROADCAST_IMPLEMENTATION_PATH) + target_sources(${TARGET} PRIVATE ${BROADCAST_IMPLEMENTATION_PATH}/Tuner.cpp) + else() + include(GetExternalCode) + set(BROADCAST_IMPLEMENTATION_VERSION "master" CACHE STRING "broadcast implementation version: commit_id_or_tag_or_branch_name") + set(BROADCAST_IMPLEMENTATION_REPOSITORY "https://code.rdkcentral.com/r/soc/broadcom/components/rdkcentral/thunder/broadcast" CACHE STRING "broadcast implementation repository") + GetExternalCode( + GIT_REPOSITORY ${BROADCAST_IMPLEMENTATION_REPOSITORY} + GIT_VERSION ${BROADCAST_IMPLEMENTATION_VERSION} + SOURCE_DIR "Implementation/Nexus" + ) + target_sources(${TARGET} PRIVATE Implementation/Nexus/Tuner.cpp) + endif() + + target_link_libraries(${TARGET} + PRIVATE + NEXUS::NEXUS + NXCLIENT::NXCLIENT + ) +else() + target_sources(${TARGET} PRIVATE Implementation/V4L/Tuner.cpp) +endif() + +set_target_properties(${TARGET} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + PUBLIC_HEADER "${PUBLIC_HEADERS}" # specify the public headers + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + ) + +target_include_directories( ${TARGET} + PUBLIC + $ + $ + $ + ) + +# =========================================================================================== +# Install ARTIFACTS: +# =========================================================================================== +install( + TARGETS ${TARGET} EXPORT ${TARGET}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/broadcast COMPONENT ${NAMESPACE}_Development + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/broadcast # headers +) + +# =========================================================================================== +# Install METADATA: +# =========================================================================================== +InstallPackageConfig( + TARGETS ${TARGET} + DESCRIPTION "${PROJECT_DESCRIPTION}" ) + +InstallCMakeConfig( + TARGETS ${TARGET}) + +add_subdirectory(test) diff --git a/Source/extensions/broadcast/Definitions.cpp b/Source/extensions/broadcast/Definitions.cpp new file mode 100644 index 000000000..48e301eb9 --- /dev/null +++ b/Source/extensions/broadcast/Definitions.cpp @@ -0,0 +1,74 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Definitions.h" + +namespace Thunder { + +ENUM_CONVERSION_BEGIN(Broadcast::ITuner::DTVStandard) + { Broadcast::ITuner::DVB, _TXT("DVB") }, + { Broadcast::ITuner::ATSC, _TXT("ATSC") }, + { Broadcast::ITuner::ISDB, _TXT("ISDB") }, + { Broadcast::ITuner::DAB, _TXT("DAB") }, +ENUM_CONVERSION_END(Broadcast::ITuner::DTVStandard) + +ENUM_CONVERSION_BEGIN(Broadcast::ITuner::annex) + { Broadcast::ITuner::NoAnnex, _TXT("None") }, + { Broadcast::ITuner::A, _TXT("A") }, + { Broadcast::ITuner::B, _TXT("B") }, + { Broadcast::ITuner::C, _TXT("C") }, +ENUM_CONVERSION_END(Broadcast::ITuner::annex) + +ENUM_CONVERSION_BEGIN(Broadcast::ITuner::modus) + { Broadcast::ITuner::Cable, _TXT("Cable") }, + { Broadcast::ITuner::Terrestrial, _TXT("Terrestrial") }, + { Broadcast::ITuner::Satellite, _TXT("Satellite") }, +ENUM_CONVERSION_END(Broadcast::ITuner::modus) + +ENUM_CONVERSION_BEGIN(Broadcast::SpectralInversion) + { Broadcast::Auto, _TXT("Auto") }, + { Broadcast::Normal, _TXT("Normal") }, + { Broadcast::Inverted, _TXT("Inverted") }, +ENUM_CONVERSION_END(Broadcast::SpectralInversion) + +ENUM_CONVERSION_BEGIN(Broadcast::Modulation) + { Broadcast::HORIZONTAL_QPSK, _TXT("QPSK_H") }, + { Broadcast::HORIZONTAL_8PSK, _TXT("8PSK_H") }, + { Broadcast::HORIZONTAL_QAM16, _TXT("QAM16_H") }, + { Broadcast::VERTICAL_QPSK, _TXT("QPSK_V") }, + { Broadcast::VERTICAL_8PSK, _TXT("8PSK_V") }, + { Broadcast::VERTICAL_QAM16, _TXT("QAM16_V") }, + { Broadcast::LEFT_QPSK, _TXT("QPSK_L") }, + { Broadcast::LEFT_8PSK, _TXT("8PSK_L") }, + { Broadcast::LEFT_QAM16, _TXT("QAM16_L") }, + { Broadcast::RIGHT_QPSK, _TXT("QPSK_R") }, + { Broadcast::RIGHT_8PSK, _TXT("8PSK_R") }, + { Broadcast::RIGHT_QAM16, _TXT("QAM16_R") }, + { Broadcast::QAM16, _TXT("QAM16") }, + { Broadcast::QAM32, _TXT("QAM32") }, + { Broadcast::QAM64, _TXT("QAM64") }, + { Broadcast::QAM128, _TXT("QAM128") }, + { Broadcast::QAM256, _TXT("QAM256") }, + { Broadcast::QAM512, _TXT("QAM512") }, + { Broadcast::QAM1024, _TXT("QAM1024") }, + { Broadcast::QAM2048, _TXT("QAM2048") }, + { Broadcast::QAM4096, _TXT("QAM4096") }, +ENUM_CONVERSION_END(Broadcast::Modulation) + +} // namespace Thunder diff --git a/Source/extensions/broadcast/Definitions.h b/Source/extensions/broadcast/Definitions.h new file mode 100644 index 000000000..e7d26351f --- /dev/null +++ b/Source/extensions/broadcast/Definitions.h @@ -0,0 +1,372 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __BROADCAST_DEFINITIONS_H +#define __BROADCAST_DEFINITIONS_H + +#include "Module.h" + +namespace Thunder { + +namespace Broadcast { + + namespace MPEG { + + class Section; + } + + struct ISection { + virtual ~ISection() {} + virtual void Handle(const MPEG::Section& section) = 0; + }; + + struct IMonitor { + virtual ~IMonitor() {} + virtual void ChangePid(const uint16_t newpid, ISection* observer) = 0; + }; + + template + class IteratorType { + public: + IteratorType() + : _position(0) + , _index() + , _list() + { + } + IteratorType(const std::map& container) + : _position(0) + , _index() + , _list() + { + typename std::map::const_iterator index(container.begin()); + while (index != container.end()) { + _list.emplace_back(index->second); + index++; + } + } + IteratorType(const IteratorType& copy) + : _list() + { + operator=(copy); + } + ~IteratorType() + { + } + + IteratorType& operator=(const IteratorType& rhs) + { + _position = rhs._position; + _list.clear(); + typename std::list::const_iterator index(rhs._list.begin()); + + while (index != rhs._list.end()) { + _list.emplace_back(*index); + if (_list.size() == rhs._position) { + _index = --_list.end(); + } + index++; + } + return (*this); + } + + public: + bool IsValid() const + { + return ((_position != 0) && (_position <= _list.size())); + } + void Reset() + { + _position = 0; + } + bool Next() + { + if (_position == 0) { + _index = _list.begin(); + _position++; + } else if (_position <= _list.size()) { + _index++; + _position++; + } + + return (_position <= _list.size()); + } + const LISTOBJECT& Current() const + { + + ASSERT(IsValid() == true); + + return (*_index); + } + + private: + uint32_t _position; + typename std::list::const_iterator _index; + typename std::list _list; + }; + + template + TYPE ConvertBCD(const uint8_t buffer[], const uint8_t digits, const bool evenStart) + { + TYPE value = 0; + for (uint8_t index = 0; index < digits;) { + value *= 10; + if (evenStart == true) { + value += ((index & 0x01) ? (buffer[index / 2] & 0xF) : (buffer[index / 2] >> 4)); + index++; + } else { + index++; + value += ((index & 0x01) ? (buffer[index / 2] & 0xF) : (buffer[index / 2] >> 4)); + } + } + return (value); + } + + enum SpectralInversion { + Auto, + Normal, + Inverted + }; + + enum Modulation { + MODULATION_UNKNOWN = 0, + + // Satellite modulation types + HORIZONTAL_QPSK = 1, + HORIZONTAL_8PSK = 2, + HORIZONTAL_QAM16 = 3, + VERTICAL_QPSK = 5, + VERTICAL_8PSK = 6, + VERTICAL_QAM16 = 7, + LEFT_QPSK = 9, + LEFT_8PSK = 10, + LEFT_QAM16 = 11, + RIGHT_QPSK = 13, + RIGHT_8PSK = 14, + RIGHT_QAM16 = 15, + + // Cable/Terestrial modulation types + QAM16 = 16, + QAM32 = 32, + QAM64 = 64, + QAM128 = 128, + QAM256 = 256, + QAM512 = 512, + QAM1024 = 1024, + QAM2048 = 2048, + QAM4096 = 4096 + }; + + enum fec { + FEC_INNER_UNKNOWN = 0, + FEC_1_2 = 1, + FEC_2_3 = 2, + FEC_3_4 = 3, + FEC_5_6 = 4, + FEC_7_8 = 5, + FEC_8_9 = 6, + FEC_3_5 = 7, + FEC_4_5 = 8, + FEC_9_10 = 9, + FEC_2_5 = 10, + FEC_6_7 = 11, + FEC_INNER_NONE = 15 + }; + + enum fec_outer { + FEC_OUTER_UNKNOWN = 0, + FEC_OUTER_NONE = 1, + RS = 2 + }; + + enum transmission { + TRANSMISSION_AUTO, + TRANSMISSION_1K, + TRANSMISSION_2K, + TRANSMISSION_4K, + TRANSMISSION_8K, + TRANSMISSION_16K, + TRANSMISSION_32K, + TRANSMISSION_C3780, + TRANSMISSION_C1 + }; + + enum guard { + GUARD_AUTO, + GUARD_1_4, + GUARD_1_8, + GUARD_1_16, + GUARD_1_32, + GUARD_1_128, + GUARD_19_128, + GUARD_19_256, + }; + + enum hierarchy { + NoHierarchy, + AutoHierarchy, + Hierarchy1, + Hierarchy2, + Hierarchy4, + }; + + struct EXTERNAL ITuner { + + struct INotification { + virtual ~INotification() {} + + virtual void Activated(ITuner* tuner) = 0; + virtual void Deactivated(ITuner* tuner) = 0; + virtual void StateChange(ITuner* tuner) = 0; + }; + + struct ICallback { + virtual ~ICallback() {} + + virtual void StateChange() = 0; + }; + + ITuner() : _adminLock(), _callback(nullptr) {} + virtual ~ITuner() {} + + enum state { + IDLE = 0x01, + LOCKED = 0x02, + PREPARED = 0x04, + STREAMING = 0x08 + }; + + enum DTVStandard { + DVB = 0x1000, + ATSC = 0x2000, + ISDB = 0x3000, + DAB = 0x4000 + }; + + enum modus { + Satellite = 0x1, + Terrestrial = 0x2, + Cable = 0x3 + }; + + enum annex { + NoAnnex = 0x000, // NoAnnex -> S/T + A = 0x400, // A -> S2/T2 + B = 0x800, + C = 0xC00 + }; + + // The following methods will be called before any create is called. It allows for an initialization, + // if requires, and a deinitialization, if the Tuners will no longer be used. + static uint32_t Initialize(const string& configuration); + static uint32_t Deinitialize(); + + // See if the tuner supports the requested mode, or is configured for the requested mode. This method + // only returns proper values if the Initialize has been called before. + static bool IsSupported(const ITuner::modus mode); + + // Accessor to create a tuner. + static ITuner* Create(const string& info); + + // Accessor to metadata on the tuners. + static void Register(INotification* notify); + static void Unregister(INotification* notify); + + // Offer the ability to get the proper information (with respect to hardware properties) of this tuner instance. + virtual uint32_t Properties() const = 0; + + inline annex Annex() const { + return (static_cast(Properties() & 0x0F00)); + } + inline modus Modus() const { + return (static_cast(Properties() & 0xF)); + } + inline DTVStandard Standard() const { + return (static_cast(Properties() & 0xF000)); + } + + // Currently locked on ID + // This method return a unique number that will identify the locked on Transport stream. The ID will always + // identify the uniquely locked on to Tune request. ID => 0 is reserved and means not locked on to anything. + virtual uint16_t Id() const = 0; + + // Using these methods the state of the tuner can be viewed. + // IDLE: Means there is no request, or the frequency requested (with other parameters) can not be locked. + // LOCKED: The stream has been locked, frequency, modulation, symbolrate and spectral inversion seem to be fine. + // PREPARED: The program that was requetsed to prepare fore, has been found in PAT/PMT, the needed information, + // like PIDS is loaded. If Priming is available, this means that the priming has started! + // STREAMING: This means that the requested program is being streamed to decoder or file, depending on implementation/inpuy. + virtual state State() const = 0; + + // Using the next method, the allocated Frontend will try to lock the channel that is found at the given parameters. + // Frequency is always in MHz. + virtual uint32_t Tune(const uint16_t frequency, const Modulation, const uint32_t symbolRate, const uint16_t fec, const SpectralInversion) = 0; + + // In case the tuner needs to be tuned to s apecific programId, please list it here. Once the PID's associated to this + // programId have been found, and set, the Tuner will reach its PREPARED state. + virtual uint32_t Prepare(const uint16_t programId) = 0; + + // A Tuner can be used to filter PSI/SI. Using the next call a callback can be installed to receive sections associated + // with a table. Each valid section received will be offered as a single section on the ISection interface for the user + // to process. + virtual uint32_t Filter(const uint16_t pid, const uint8_t tableId, ISection* callback) = 0; + + // Using the next two methods, the frontends will be hooked up to decoders or file, and be removed from a decoder or file. + virtual uint32_t Attach(const uint8_t index) = 0; + virtual uint32_t Detach(const uint8_t index) = 0; + + + // If you have an ITuner interface, you can subscribe to state changes of this Tuner interface + // This will only be one instance, by design, to avoid the overhead of maintining a list and + // thus spending more resources. The idea is that the object holding the ITuner interface to + // do the actual tune can assign a callback and receive the actual state changes. If you "just" + // receive this interface on the INotification, you should *NOT* set a callback on this interface + void Callback(ICallback* callback) { + + _adminLock.Lock(); + + ASSERT ((_callback == nullptr) ^ (callback == nullptr)); + + _callback = callback; + + _adminLock.Unlock(); + } + void StateChange() { + _adminLock.Lock(); + if (_callback != nullptr) { + _callback->StateChange(); + } + _adminLock.Unlock(); + } + void Lock() const { + _adminLock.Lock(); + } + void Unlock() const { + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + ICallback* _callback; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // __BROADCAST_DEFINITIONS_H diff --git a/Source/extensions/broadcast/Descriptors.h b/Source/extensions/broadcast/Descriptors.h new file mode 100644 index 000000000..8d8c06672 --- /dev/null +++ b/Source/extensions/broadcast/Descriptors.h @@ -0,0 +1,229 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DESCRIPTORS_H +#define DESCRIPTORS_H + +#include "Definitions.h" +#include "MPEGDescriptor.h" + +namespace Thunder { +namespace Broadcast { + namespace DVB { + namespace Descriptors { + + class EXTERNAL NetworkName { + private: + NetworkName operator=(const NetworkName& rhs) = delete; + + public: + constexpr static uint8_t TAG = 0x40; + + public: + NetworkName() + : _data() + { + } + NetworkName(const NetworkName& copy) + : _data(copy._data) + { + } + NetworkName(const MPEG::Descriptor& copy) + : _data(copy) + { + } + ~NetworkName() + { + } + + public: + string Name() const + { + return (Core::ToString(reinterpret_cast(&(_data[1])), _data[0])); + } + + private: + MPEG::Descriptor _data; + }; + + class EXTERNAL SatelliteDeliverySystem { + private: + SatelliteDeliverySystem operator=(const SatelliteDeliverySystem& rhs) = delete; + + public: + constexpr static uint8_t TAG = 0x43; + + public: + SatelliteDeliverySystem() + : _data() + { + } + SatelliteDeliverySystem(const SatelliteDeliverySystem& copy) + : _data(copy._data) + { + } + SatelliteDeliverySystem(const MPEG::Descriptor& copy) + : _data(copy) + { + } + ~SatelliteDeliverySystem() + { + } + + public: + // Frequency in KHz + uint32_t Frequency() const + { + return (Broadcast::ConvertBCD(&(_data[0]), 8, true) * 10); + } + Broadcast::Modulation Modulation() const + { + return (static_cast(((_data[6] >> 3) & 0x0C) | (_data[6] & 0x03))); + } + uint32_t SymbolRate() const + { + return (Broadcast::ConvertBCD(&(_data[7]), 7, true)); + } + fec FECInner() const + { + return (static_cast(_data[5] & 0xF)); + } + + private: + MPEG::Descriptor _data; + }; + + class EXTERNAL CableDeliverySystem { + private: + CableDeliverySystem operator=(const CableDeliverySystem& rhs) = delete; + + public: + constexpr static uint8_t TAG = 0x44; + + public: + CableDeliverySystem() + : _data() + { + } + CableDeliverySystem(const CableDeliverySystem& copy) + : _data(copy._data) + { + } + CableDeliverySystem(const MPEG::Descriptor& copy) + : _data(copy) + { + } + ~CableDeliverySystem() + { + } + + public: + // Frequency in KHz + uint32_t Frequency() const + { + return (Broadcast::ConvertBCD(&(_data[0]), 8, true) / 10); + } + Broadcast::Modulation Modulation() const + { + uint8_t mod(_data[6]); + return (static_cast(((mod == 0) || (mod > 9)) ? 0 : (0x10 << (mod - 1)))); + } + uint32_t SymbolRate() const + { + return (Broadcast::ConvertBCD(&(_data[7]), 7, true)); + } + fec FECInner() const + { + return (static_cast(_data[5] & 0xF)); + } + fec_outer FECOuter() const + { + return (static_cast(_data[10] & 0x3)); + } + + private: + MPEG::Descriptor _data; + }; + + class EXTERNAL Service { + private: + Service operator=(const Service& rhs) = delete; + + public: + constexpr static uint8_t TAG = 0x48; + + enum type { + DIGITAL_TELEVISION = 0x01, + DIGITAL_RADIO = 0x02, + TELETEXT = 0x03, + NVOD_REFERENCE = 0x04, + NVOD_TIME_SHIFT = 0x05, + MOSAIC = 0x06, + ADVANCED_DIGITAL_RADIO = 0x0A, + ADVANCED_DIGITAL_MOSAIC = 0x0B, + DATA_BROADCAST_SERVICE = 0x0C, + ADVANCED_SD_TELEVISION = 0x16, + ADVANCED_SD_NVOD_TIME_SHIFT = 0x17, + ADVANCED_SD_NVOD_REFERENCE = 0x18, + ADVANCED_HD_TELEVISION = 0x19, + ADVANCED_HD_NVOD_TIME_SHIFT = 0x1A, + ADVANCED_HD_NVOD_REFERENCE = 0x1B + }; + + public: + Service() + : _data() + { + } + Service(const Service& copy) + : _data(copy._data) + { + } + Service(const MPEG::Descriptor& copy) + : _data(copy) + { + } + ~Service() + { + } + + public: + type Type() const + { + return (static_cast(_data[0])); + } + string Provider() const + { + return (Core::ToString(reinterpret_cast(&(_data[2])), _data[1])); + } + string Name() const + { + uint8_t offset = 1 /* service type */ + 1 /* length */ + _data[1]; + return (Core::ToString(reinterpret_cast(&(_data[offset + 1])), _data[offset])); + } + + private: + MPEG::Descriptor _data; + }; + } + } +} +} // namespace Thunder::Broadcast::DVB::Descriptors + +#endif // DESCRIPTORS_H diff --git a/Source/extensions/broadcast/EIT.h b/Source/extensions/broadcast/EIT.h new file mode 100644 index 000000000..4bdc331cd --- /dev/null +++ b/Source/extensions/broadcast/EIT.h @@ -0,0 +1,203 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DVB_EIT_TABLE_H +#define DVB_EIT_TABLE_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace DVB { + + class EXTERNAL EIT { + public: + static const uint16_t ACTUAL = 0x42; + static const uint16_t OTHER = 0x46; + + public: + enum running { + Undefined = 0, + NotRunning = 1, + AboutToStart = 2, + Pausing = 3, + Running = 4 + }; + + public: + class ServiceIterator { + public: + ServiceIterator() + : _info() + , _offset(~0) + { + } + ServiceIterator(const Core::DataElement& data) + : _info(data) + , _offset(~0) + { + } + ServiceIterator(const ServiceIterator& copy) + : _info(copy._info) + , _offset(copy._offset) + { + } + ~ServiceIterator() {} + + ServiceIterator& operator=(const ServiceIterator& RHS) + { + _info = RHS._info; + _offset = RHS._offset; + + return (*this); + } + + public: + inline bool IsValid() const { return (_offset < _info.Size()); } + inline void Reset() { _offset = ~0; } + inline bool Next() + { + if (_offset == static_cast(~0)) { + _offset = 0; + } else if (_offset < _info.Size()) { + _offset += (DescriptorSize() + 5); + } + + return (IsValid()); + } + inline bool EIT_PF() const + { + return ((_info[_offset + 2] & 0x01) != 0); + } + inline bool EIT_Schedule() const + { + return ((_info[_offset + 2] & 0x02) != 0); + } + inline bool IsFreeToAir() const + { + return ((_info[_offset + 3] & 0x10) != 0); + } + inline running RunningMode() const + { + return (static_cast((_info[_offset + 3] & 0xE0) >> 5)); + } + inline uint16_t ServiceId() const + { + return ((_info[_offset + 0] << 8) | _info[_offset + 1]); + } + inline MPEG::DescriptorIterator Descriptors() const + { + return (MPEG::DescriptorIterator( + Core::DataElement(_info, _offset + 5, DescriptorSize()))); + } + inline uint8_t Services() const + { + uint8_t count = 0; + uint16_t offset = 0; + while (offset < _info.Size()) { + offset += (((_info[offset + 3] << 8) | _info[offset + 4]) & 0x0FFF) + 5; + count++; + } + return (count); + } + + private: + inline uint16_t DescriptorSize() const + { + return ((_info[_offset + 3] << 8) | _info[_offset + 4]) & 0x0FFF; + } + + private: + Core::DataElement _info; + uint16_t _offset; + }; + + public: + EIT() + : _data() + , _transportStreamId(~0) + { + } + EIT(const MPEG::Table& data) + : _data(data.Data()) + , _transportStreamId(data.Extension()) + { + } + EIT(const uint16_t transportStreamId, const Core::DataElement& data) + : _data(data) + , _transportStreamId(transportStreamId) + { + } + EIT(const EIT& copy) + : _data(copy._data) + , _transportStreamId(copy._transportStreamId) + { + } + ~EIT() {} + + EIT& operator=(const EIT& rhs) + { + _data = rhs._data; + _transportStreamId = rhs._transportStreamId; + return (*this); + } + bool operator==(const EIT& rhs) const + { + return ((_transportStreamId == rhs._transportStreamId) && (_data == rhs._data)); + } + bool operator!=(const EIT& rhs) const { return (!operator==(rhs)); } + + public: + inline bool IsValid() const + { + return ((_transportStreamId != static_cast(~0)) && (_data.Size() >= 2)); + } + inline uint16_t TransportStreamId() const { return (_transportStreamId); } + uint16_t OriginalNetworkId() const + { + return (_data.GetNumber(0) & 0x1FFF); + } + ServiceIterator Services() const + { + return (ServiceIterator(Core::DataElement(_data, 3, _data.Size() - 3))); + } + + private: + Core::DataElement _data; + uint16_t _transportStreamId; + }; + + } // namespace DVB +} // namespace Broadcast +} // namespace Thunder + +#endif // DVB_EIT_TABLE_H diff --git a/Source/extensions/broadcast/Implementation/V4L/Tuner.cpp b/Source/extensions/broadcast/Implementation/V4L/Tuner.cpp new file mode 100644 index 000000000..0048bb59a --- /dev/null +++ b/Source/extensions/broadcast/Implementation/V4L/Tuner.cpp @@ -0,0 +1,841 @@ + + /* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Definitions.h" +#include "ProgramTable.h" +#include "TunerAdministrator.h" + +#include +#include +#include + +#if (DVB_API_VERSION < 5) +#error "Not supported DVB API version" +#endif + +#define __DEBUG__ + +// -------------------------------------------------------------------- +// SOURCE: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/dvb +// -------------------------------------------------------------------- +namespace Thunder { +namespace Broadcast { + + struct conversion_entry { + int from; // Thunder Broadcast/ITuner value + int to; // LinuxDVB API value + }; + + static constexpr conversion_entry _tableSystemType[] = { + { (ITuner::DVB | ITuner::Cable | ITuner::B), SYS_DVBC_ANNEX_B }, + { (ITuner::DVB | ITuner::Cable | ITuner::C), SYS_DVBC_ANNEX_C }, + { (ITuner::DVB | ITuner::Terrestrial | ITuner::NoAnnex), SYS_DVBT }, + { (ITuner::DVB | ITuner::Terrestrial | ITuner::A), SYS_DVBT2 }, + { (ITuner::DVB | ITuner::Satellite | ITuner::NoAnnex), SYS_DVBS }, + { (ITuner::DVB | ITuner::Satellite | ITuner::A), SYS_DVBS2 }, + { (ITuner::ISDB | ITuner::Satellite | ITuner::NoAnnex), SYS_ISDBS }, + { (ITuner::ISDB | ITuner::Terrestrial | ITuner::NoAnnex), SYS_ISDBT }, + { (ITuner::ISDB | ITuner::Cable | ITuner::NoAnnex), SYS_ISDBC } + }; + + static constexpr conversion_entry _tableInversion[] = { + { Broadcast::Auto, INVERSION_AUTO }, + { Broadcast::Normal, INVERSION_OFF }, + { Broadcast::Inverted, INVERSION_ON } + }; + static constexpr conversion_entry _tableFEC[] = { + { Broadcast::FEC_INNER_NONE, FEC_NONE }, + { Broadcast::FEC_INNER_UNKNOWN, FEC_AUTO }, + { Broadcast::FEC_1_2, FEC_1_2 }, + { Broadcast::FEC_2_3, FEC_2_3 }, + { Broadcast::FEC_2_5, FEC_2_5 }, + { Broadcast::FEC_3_4, FEC_3_4 }, + { Broadcast::FEC_3_5, FEC_3_5 }, + { Broadcast::FEC_4_5, FEC_4_5 }, + { Broadcast::FEC_5_6, FEC_5_6 }, + { Broadcast::FEC_6_7, FEC_6_7 }, + { Broadcast::FEC_7_8, FEC_7_8 }, + { Broadcast::FEC_8_9, FEC_8_9 }, + { Broadcast::FEC_9_10, FEC_9_10 } + }; + static constexpr conversion_entry _tableModulation[] = { + { Broadcast::HORIZONTAL_QPSK, QPSK }, + { Broadcast::VERTICAL_QPSK, QPSK }, + { Broadcast::LEFT_QPSK, QPSK }, + { Broadcast::RIGHT_QPSK, QPSK }, + { Broadcast::HORIZONTAL_8PSK, PSK_8 }, + { Broadcast::VERTICAL_8PSK, PSK_8 }, + { Broadcast::LEFT_8PSK, PSK_8 }, + { Broadcast::RIGHT_8PSK, PSK_8 }, + { Broadcast::QAM16, QAM_16 }, + { Broadcast::QAM32, QAM_32 }, + { Broadcast::QAM64, QAM_64 }, + { Broadcast::QAM128, QAM_128 }, + { Broadcast::QAM256, QAM_256 }, + }; + static constexpr conversion_entry _tableTransmission[] = { + { Broadcast::TRANSMISSION_AUTO, TRANSMISSION_MODE_AUTO }, + { Broadcast::TRANSMISSION_1K, TRANSMISSION_MODE_1K }, + { Broadcast::TRANSMISSION_2K, TRANSMISSION_MODE_2K }, + { Broadcast::TRANSMISSION_4K, TRANSMISSION_MODE_4K }, + { Broadcast::TRANSMISSION_8K, TRANSMISSION_MODE_8K }, + { Broadcast::TRANSMISSION_16K, TRANSMISSION_MODE_16K }, + { Broadcast::TRANSMISSION_32K, TRANSMISSION_MODE_32K }, + { Broadcast::TRANSMISSION_C3780, TRANSMISSION_MODE_C3780 }, + { Broadcast::TRANSMISSION_C1, TRANSMISSION_MODE_C1 } + }; + static constexpr conversion_entry _tableGuard[] = { + { Broadcast::GUARD_AUTO, GUARD_INTERVAL_AUTO }, + { Broadcast::GUARD_1_4, GUARD_INTERVAL_1_4 }, + { Broadcast::GUARD_1_8, GUARD_INTERVAL_1_8 }, + { Broadcast::GUARD_1_16, GUARD_INTERVAL_1_16 }, + { Broadcast::GUARD_1_32, GUARD_INTERVAL_1_32 }, + { Broadcast::GUARD_1_128, GUARD_INTERVAL_1_128 }, + { Broadcast::GUARD_19_128, GUARD_INTERVAL_19_128 }, + { Broadcast::GUARD_19_256, GUARD_INTERVAL_19_256 } + }; + static constexpr conversion_entry _tableHierarchy[] = { + { Broadcast::NoHierarchy, HIERARCHY_NONE }, + { Broadcast::AutoHierarchy, HIERARCHY_AUTO }, + { Broadcast::Hierarchy1, HIERARCHY_1 }, + { Broadcast::Hierarchy2, HIERARCHY_2 }, + { Broadcast::Hierarchy4, HIERARCHY_4 } + }; + /* +static constexpr conversion_entry _tablePilot[] = { + { Broadcast::PILOT_AUTO, PILOT_AUTO }, + { Broadcast::PILOT_ON, PILOT_ON }, + { Broadcast::PILOT_OFF, PILOT_OFF } +}; +static constexpr conversion_entry _tableRollOff[] = { + { Broadcast::HIERARCHY_AUTO, ROLLOFF_AUTO }, + { Broadcast::ROLLOFF_20, ROLLOFF_20 }, + { Broadcast::ROLLOFF_25, ROLLOFF_25 }, + { Broadcast::ROLLOFF_35, ROLLOFF_35 } +}; +*/ + + template + int Convert(const conversion_entry (&table)[N], const int from, const int ifnotfound) + { + uint16_t index = 0; + while ((index < N) && (from != table[index].from)) { + index++; + } + return (index < N ? table[index].to : ifnotfound); + } + + void Property(dtv_property& property, const int command, const int value) + { + property.cmd = command; + property.u.data = value; + } + + static uint16_t IndexToFrontend(const uint8_t /* index */) + { + uint8_t adapter = 0; + uint8_t frontend = 0; + + // Count the number of frontends you have per adapter, substract those from the index.. + + return ((adapter << 8) | frontend); + } + + class __attribute__((visibility("hidden"))) Tuner : public ITuner { + private: + Tuner() = delete; + Tuner(const Tuner&) = delete; + Tuner& operator=(const Tuner&) = delete; + + class Observer : public Core::Thread { + public: + Observer(const Observer&) = delete; + Observer& operator=(const Observer&) = delete; + + Observer() : Core::Thread(Thread::DefaultStackSize(), _T("Tuner")), _adminLock(), _entries() { + } + ~Observer() override { + } + + public: + static Observer& Instance() + { + static Observer& _instance = Core::SingletonType::Instance(); + return (_instance); + } + void Register(Tuner& callback) { + _adminLock.Lock(); + ASSERT (std::find(_entries.begin(), _entries.end(), &callback) == _entries.end()); + _entries.push_back(&callback); + Thread::Run(); + _adminLock.Unlock(); + } + void Unregister(Tuner& callback) { + _adminLock.Lock(); + std::list::iterator index (std::find(_entries.begin(), _entries.end(), &callback)); + if (index != _entries.end()) { + _entries.erase(index); + } + _adminLock.Unlock(); + } + + private: + uint32_t Worker() override { + Block(); + _adminLock.Lock(); + std::list::iterator index (_entries.begin()); + uint32_t result = (_entries.size() > 0 ? 100 : Core::infinite); // Slots of 100 mS; + while (index != _entries.end()) { + if ((*index)->Dispatch() == true) { + index = _entries.erase(index); + } + else { + index++; + } + } + _adminLock.Unlock(); + return (result); + } + + private: + Core::CriticalSection _adminLock; + std::list _entries; + }; + class MuxFilter : public Core::IResource { + public: + MuxFilter() = delete; + MuxFilter(const MuxFilter&) = delete; + MuxFilter& operator= (const MuxFilter&) = delete; + + MuxFilter(const string& path, const uint8_t index, const uint16_t pid, const uint8_t tableId, ISection* callback) + : _mux(-1) + , _offset(0) + , _length(0) + , _size(1024) + , _buffer(reinterpret_cast(::malloc(_size))) + , _callback(callback) { + + static constexpr TCHAR MuxSuffix[] = _T("demux"); + + char deviceName[50]; + char strIndex[4]; + + ::snprintf(strIndex, sizeof(strIndex), "%d", index); + ASSERT(sizeof(deviceName) > (path.size() + strlen(MuxSuffix) + strlen(strIndex))); + + ::snprintf(deviceName, sizeof(deviceName), "%s%s%s", path.c_str(), MuxSuffix, strIndex); + + _mux = open(deviceName, O_RDWR|O_NONBLOCK); + + if (_mux == -1) { + TRACE_L1("Could not open the filter[%s]: %d\n", deviceName, errno); + } + else { + + struct dmx_sct_filter_params sctFilterParams; + + sctFilterParams.pid = pid; + ::memset(&sctFilterParams.filter, 0, sizeof(sctFilterParams.filter)); + sctFilterParams.timeout = 0; + sctFilterParams.flags = DMX_IMMEDIATE_START|DMX_CHECK_CRC; + sctFilterParams.filter.filter[0] = tableId; + sctFilterParams.filter.mask[0] = 0xFF; + + if (ioctl(_mux, DMX_SET_FILTER, &sctFilterParams) < 0) { + TRACE_L1("Could not configue the filter[%d,%d]: %d\n", pid, tableId, errno); + ::close(_mux); + _mux = -1; + } + else { + Core::ResourceMonitor::Instance().Register(*this); + } + } + } + ~MuxFilter() { + if (_mux != -1) { + Core::ResourceMonitor::Instance().Unregister(*this); + ::close(_mux); + } + ::free(_buffer); + } + + public: + bool IsValid() const { + return (_mux != -1); + } + handle Descriptor() const override { + return (_mux); + } + uint16_t Events() override { + return (POLLPRI); + } + void Handle(const uint16_t events VARIABLE_IS_NOT_USED) override { + if (LoadHeader() == true) { + int loaded = ::read(_mux, &(_buffer[_offset]), (_length - (_offset - 3))); + if (loaded < 0) { + int result = errno; + if ((result != EAGAIN) || (result != EWOULDBLOCK)) { + _offset = 0; + } + } + else { + _offset += loaded; + if ((_offset - 3) == _length) { + MPEG::Section newSection(Core::DataElement(_offset, _buffer)); + _callback->Handle(newSection); + _offset = 0; + } + } + } + } + + private: + bool LoadHeader() + { + bool moreToLoad = true; + if (_offset < 3) { + int loaded = ::read(_mux, &(_buffer[_offset]), (3 - _offset)); + if (loaded < 0) { + int result = errno; + if ((result != EAGAIN) || (result != EWOULDBLOCK)) { + _offset = 0; + } + moreToLoad = false; + } + else { + _offset += loaded; + if (_offset != 3) { + moreToLoad = false; + } + else { + _length = (_buffer[1] << 8) | (_buffer[2] & 0xFF); + if ((_length + 3) > _size) { + uint8_t copy = _buffer[0]; + ::free (_buffer); + _size = _length + 3; + _buffer = reinterpret_cast(::malloc(_size)); + _buffer[0] = copy; + _buffer[1] = (_length >> 8) & 0xFF; + _buffer[2] = _length & 0xFF; + } + } + } + } + return (moreToLoad); + } + + + private: + int _mux; + uint16_t _offset; + uint16_t _length; + uint16_t _size; + uint8_t* _buffer; + ISection* _callback; + }; + + public: + class Information { + private: + Information(const Information&) = delete; + Information& operator=(const Information&) = delete; + + private: + class Config : public Core::JSON::Container { + private: + Config(const Config&); + Config& operator=(const Config&); + + public: + Config() + : Core::JSON::Container() + , Frontends(1) + , Decoders(1) + , Standard(ITuner::DVB) + , Annex(ITuner::A) + , Modus(ITuner::Terrestrial) + , Scan(false) + , Callsign("Streamer") + { + Add(_T("frontends"), &Frontends); + Add(_T("decoders"), &Decoders); + Add(_T("standard"), &Standard); + Add(_T("annex"), &Annex); + Add(_T("modus"), &Modus); + Add(_T("scan"), &Scan); + Add(_T("callsign"), &Callsign); + } + ~Config() + { + } + + public: + Core::JSON::DecUInt8 Frontends; + Core::JSON::DecUInt8 Decoders; + Core::JSON::EnumType Standard; + Core::JSON::EnumType Annex; + Core::JSON::EnumType Modus; + Core::JSON::Boolean Scan; + Core::JSON::String Callsign; + }; + + Information() + : _frontends(0) + , _standard() + , _annex() + , _modus() + , _type(SYS_UNDEFINED) + , _scan(false) + { + } + + public: + static Information& Instance() + { + return (_instance); + } + ~Information() + { + } + void Initialize(const string& configuration) + { + + Config config; + config.FromString(configuration); + + _frontends = config.Frontends.Value(); + _standard = config.Standard.Value(); + _annex = config.Annex.Value(); + _scan = config.Scan.Value(); + _modus = config.Modus.Value(); + + _type = Convert(_tableSystemType, _standard | _modus | _annex, SYS_UNDEFINED); + + ASSERT(_type != SYS_UNDEFINED); + + } + void Deinitialize() + { + } + + public: + inline bool IsSupported(const ITuner::modus mode) + { + return ((_type != SYS_UNDEFINED) && (mode == _modus)); + } + inline ITuner::DTVStandard Standard() const + { + return (_standard); + } + inline ITuner::annex Annex() const + { + return (_annex); + } + inline ITuner::modus Modus() const + { + return (_modus); + } + inline int Type() const + { + return (_type); + } + inline bool Scan() const + { + return (_scan); + } + + private: + uint8_t _frontends; + ITuner::DTVStandard _standard; + ITuner::annex _annex; + ITuner::modus _modus; + int _type; + bool _scan; + + static Information _instance; + }; + + private: +PUSH_WARNING(DISABLE_WARNING_MISSING_FIELD_INITIALIZERS) + Tuner(uint8_t index, Broadcast::transmission transmission = Broadcast::TRANSMISSION_AUTO, Broadcast::guard guard = Broadcast::GUARD_AUTO, Broadcast::hierarchy hierarchy = Broadcast::AutoHierarchy) + : _state(IDLE) + , _frontend() + , _transmission() + , _guard() + , _hierarchy() + , _info({ 0 }) + , _devicePath() + , _frontindex(0) + , _callback(nullptr) + { +POP_WARNING() + _callback = TunerAdministrator::Instance().Announce(this); + if (Tuner::Information::Instance().Type() != SYS_UNDEFINED) { + char deviceName[32]; + + uint16_t info = IndexToFrontend(index); + _frontindex = (info & 0xFF); + + ::snprintf(deviceName, sizeof(deviceName), "/dev/dvb/adapter%d/", (info >> 8)); + + _devicePath = deviceName; + + ::snprintf(&(deviceName[_devicePath.length()]), (sizeof(deviceName) - _devicePath.length()), "frontend%d", _frontindex); + + _frontend = open(deviceName, O_RDWR); + + if (_frontend != -1) { + if (::ioctl(_frontend, FE_GET_INFO, &_info) == -1) { + TRACE_L1("Can not get information about the frontend. Error %d.", errno); + close(_frontend); + _frontend = -1; + } + TRACE_L1("Opened frontend %s. Second Generation Support: %s", _info.name, (IsSecondGeneration() ? _T("true") : _T("false"))); + TRACE_L1("Support auto FEC: %s", (HasAutoFEC() ? _T("true") : _T("false"))); + } else { + TRACE_L1("Can not open frontend %s error: %d.", deviceName, errno); + } + + _transmission = Convert(_tableTransmission, transmission, TRANSMISSION_MODE_AUTO); + _guard = Convert(_tableGuard, guard, GUARD_INTERVAL_AUTO); + _hierarchy = Convert(_tableHierarchy, hierarchy, HIERARCHY_AUTO); + } + } + + public: + ~Tuner() + { + TunerAdministrator::Instance().Revoke(this); + + Observer::Instance().Unregister(*this); + + Detach(0); + + if (_frontend != -1) { + close(_frontend); + } + + _callback = nullptr; + } + + static ITuner* Create(const string& info) + { + Tuner* result = nullptr; + + uint8_t index = Core::NumberType(Core::TextFragment(info)).Value(); + + result = new Tuner(index); + + if ((result != nullptr) && (result->IsValid() == false)) { + delete result; + result = nullptr; + } + + return (result); + } + + public: + bool HasAutoFEC() const { + return ((_info.caps & FE_CAN_FEC_AUTO) != 0); + } + bool IsSecondGeneration() const { + return ((_info.caps & FE_CAN_2G_MODULATION) != 0); + } + bool IsValid() const + { + return (_frontend != -1); + } + const char* Name() const + { + return (_info.name); + } + + virtual uint32_t Properties() const override + { + Information& instance = Information::Instance(); + return (instance.Annex() | instance.Standard() | + #ifdef SATELITE + ITuner::modus::Satellite + #else + ITuner::modus::Terrestrial + #endif + ); + + // ITuner::modus::Cable + } + + // Currently locked on ID + // This method return a unique number that will identify the locked on Transport stream. The ID will always + // identify the uniquely locked on to Tune request. ID => 0 is reserved and means not locked on to anything. + virtual uint16_t Id() const override + { + return 0; //(_state == IDLE ? 0 : _settings.frequency / 1000000); + } + + // Using these methods the state of the tuner can be viewed. + // IDLE: Means there is no request, or the frequency requested (with other parameters) can not be locked. + // LOCKED: The stream has been locked, frequency, modulation, symbolrate and spectral inversion seem to be fine. + // PREPARED: The program that was requetsed to prepare fore, has been found in PAT/PMT, the needed information, + // like PIDS is loaded. If Priming is available, this means that the priming has started! + // STREAMING: This means that the requested program is being streamed to decoder or file, depending on implementation/inpuy. + virtual state State() const override + { + return _state; + } + + // Using the next method, the allocated Frontend will try to lock the channel that is found at the given parameters. + // Frequency is always in MHz. + virtual uint32_t Tune(const uint16_t frequency, const Modulation modulation, const uint32_t symbolRate, const uint16_t fec, const SpectralInversion inversion) override + { + uint8_t propertyCount; + struct dtv_property props[16]; + ::memset(&props, 0, sizeof(props)); + + Property(props[0], DTV_DELIVERY_SYSTEM, Tuner::Information::Instance().Type()); + Property(props[1], DTV_FREQUENCY, frequency * 1000000); + Property(props[2], DTV_MODULATION, Convert(_tableModulation, modulation, QAM_64)); + Property(props[3], DTV_INVERSION, Convert(_tableInversion, inversion, INVERSION_AUTO)); + Property(props[4], DTV_SYMBOL_RATE, symbolRate); + Property(props[5], DTV_INNER_FEC, Convert(_tableFEC, fec, FEC_AUTO)); + propertyCount = 6; + + if (Tuner::Information::Instance().Modus() == ITuner::Terrestrial){ + Property(props[6], DTV_BANDWIDTH_HZ, symbolRate); + Property(props[7], DTV_CODE_RATE_HP, Convert(_tableFEC, (fec & 0xFF), FEC_AUTO)); + Property(props[8], DTV_CODE_RATE_LP, Convert(_tableFEC, ((fec >> 8) & 0xFF), FEC_AUTO)); + Property(props[9], DTV_TRANSMISSION_MODE, _transmission); + Property(props[10], DTV_GUARD_INTERVAL, _guard); + Property(props[11], DTV_HIERARCHY, _hierarchy); + propertyCount = 12; + } + + Property(props[propertyCount++], DTV_TUNE, 0); + + struct dtv_properties dtv_prop; + dtv_prop.num = propertyCount; + dtv_prop.props = props; + + _state = IDLE; + + if (ioctl(_frontend, FE_SET_PROPERTY, &dtv_prop) < 0) { + perror("ioctl"); + } else { + #ifdef __DEBUG__ + TRACE_L1("Tuning request send out !!!"); + TRACE_L1(" Delivery: %d ", props[0].u.data); + TRACE_L1(" Frequency: %d Hz", props[1].u.data); + TRACE_L1(" Modulation: %d ", props[2].u.data); + TRACE_L1(" Inversion: %d ", props[3].u.data); + TRACE_L1(" Symbolrate: %d ", props[4].u.data); + TRACE_L1(" Inner FEC: %d ", props[5].u.data); + if (propertyCount > 6) { + TRACE_L1(" Bandwidth: %d ", props[6].u.data); + TRACE_L1(" Coderate HP: %d ", props[7].u.data); + TRACE_L1(" Coderate LP: %d ", props[8].u.data); + TRACE_L1(" Transmission: %d ", props[9].u.data); + TRACE_L1(" Guard Interval: %d ", props[10].u.data); + TRACE_L1(" Hierarchy: %d ", props[11].u.data); + } + _lastState = 0; + #endif + Observer::Instance().Register(*this); + } + + return (Core::ERROR_NONE); + } + + // In case the tuner needs to be tuned to a specific programId, please list it here. Once the PID's associated to this + // programId have been found, and set, the Tuner will reach its PREPARED state. + virtual uint32_t Prepare(const uint16_t programId VARIABLE_IS_NOT_USED) override + { + TRACE_L1("%s:%d %s", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + + // A Tuner can be used to filter PSI/SI. Using the next call a callback can be installed to receive sections associated + // with a table. Each valid section received will be offered as a single section on the ISection interface for the user + // to process. + virtual uint32_t Filter(const uint16_t pid, const uint8_t tableId, ISection* callback) override + { + uint32_t result = Core::ERROR_UNAVAILABLE; + uint32_t id = (pid << 16) | tableId; + + _state.Lock(); + + if (callback != nullptr) { + auto entry = _filters.emplace( + std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(_devicePath, _frontindex, pid, tableId, callback)); + + if (entry.first->second.IsValid() == true) { + result = Core::ERROR_NONE; + } + else { + _filters.erase(entry.first); + result = Core::ERROR_GENERAL; + } + } else { + std::map::iterator index(_filters.find(id)); + if (index != _filters.end()) { + _filters.erase(index); + result = Core::ERROR_NONE; + } + } + + _state.Unlock(); + + return (result); + } + // Using the next two methods, the frontends will be hooked up to decoders or file, and be removed from a decoder or file. + virtual uint32_t Attach(const uint8_t index VARIABLE_IS_NOT_USED) override + { + TRACE_L1("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + virtual uint32_t Detach(const uint8_t index VARIABLE_IS_NOT_USED) override + { + TRACE_L1("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + + private: + void Lock() { + _state.Lock(); + } + void Unlock() { + _state.Unlock(); + } + bool Dispatch() { + bool remove = false; + unsigned int status; + + // IDLE = 0x01, + // FE_TIMEDOUT No lock within the last about 2 seconds. + // LOCKED = 0x02, + // FE_HAS_CARRIER Has found a signal. + // FE_HAS_LOCK Digital TV were locked and everything is working. + // FE_HAS_SIGNAL Has found something above the noise level. + // FE_HAS_SYNC Synchronization bytes was found. + // FE_HAS_VITERBI FEC inner coding (Viterbi, LDPC or other inner code). is stable. + // FE_REINIT Frontend was reinitialized, application is recommended to reset DiSEqC, tone and parameters. + // PREPARED = 0x04, + // STREAMING = 0x08 + + if (::ioctl(_frontend, FE_READ_STATUS, &status) < 0) { + TRACE_L1("Status could not be read!. Error: %d", errno); + } + else { + TRACE_L1("FE_HAS_LOCK: %s", status & FE_HAS_LOCK ? _T("true") : _T("false")); + TRACE_L1("FE_TIMEDOUT: %s", status & FE_TIMEDOUT ? _T("true") : _T("false")); + + if ((status & FE_HAS_LOCK) != 0) { + remove = true; + _state = ITuner::LOCKED; + } + else if ((status & FE_TIMEDOUT) != 0) { + _state = ITuner::IDLE; + remove = true; + } + + #ifdef __DEBUG__ + if ((status & FE_HAS_LOCK) != 0) { + uint16_t snr, signal; + uint32_t ber, uncorrected_blocks; + + TRACE_L1("Signal,SNR,BER,UNC,Status: %d,%d,%d,%d,%d", signal, snr, ber, uncorrected_blocks, status); + + if (::ioctl(_frontend, FE_READ_SIGNAL_STRENGTH, &signal) < 0) { + signal = ~0; + } + if (::ioctl(_frontend, FE_READ_SNR, &snr) < 0) { + snr = ~0; + } + if (::ioctl(_frontend, FE_READ_BER, &ber) < 0) { + ber = ~0; + } + if (::ioctl(_frontend, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks) < 0) { + uncorrected_blocks = ~0; + } + + unsigned int delta = _lastState ^ status; + if (delta & FE_HAS_SIGNAL) { TRACE_L1 ("FE_HAS_SIGNAL: %s", status & FE_HAS_SIGNAL ? _T("true") : _T("false")); } + if (delta & FE_HAS_CARRIER) { TRACE_L1 ("FE_HAS_CARRIER: %s", status & FE_HAS_CARRIER ? _T("true") : _T("false")); } + if (delta & FE_HAS_VITERBI) { TRACE_L1 ("FE_HAS_VITERBI: %s", status & FE_HAS_VITERBI ? _T("true") : _T("false")); } + if (delta & FE_HAS_SYNC) { TRACE_L1 ("FE_HAS_SYNC: %s", status & FE_HAS_SYNC ? _T("true") : _T("false")); } + if (delta & FE_TIMEDOUT) { TRACE_L1 ("FE_TIMEDOUT: %s", status & FE_TIMEDOUT ? _T("true") : _T("false")); } + if (delta & FE_REINIT) { TRACE_L1 ("FE_REINIT: %s", status & FE_REINIT ? _T("true") : _T("false")); } + if (delta & FE_HAS_LOCK) { TRACE_L1 ("FE_HAS_LOCK: %s", status & FE_HAS_LOCK ? _T("true") : _T("false")); } + _lastState = status; + } + #endif + } + + return (remove); + } + + private: + Core::StateTrigger _state; + int _frontend; + int _transmission; + int _guard; + int _hierarchy; + struct dvb_frontend_info _info; + std::map _filters; + string _devicePath; + uint8_t _frontindex; + TunerAdministrator::ICallback* _callback; + #ifdef __DEBUG__ + unsigned int _lastState; + #endif + }; + + /* static */ Tuner::Information Tuner::Information::_instance; + + // The following methods will be called before any create is called. It allows for an initialization, + // if requires, and a deinitialization, if the Tuners will no longer be used. + /* static */ uint32_t ITuner::Initialize(const string& configuration) + { + Tuner::Information::Instance().Initialize(configuration); + + return (Core::ERROR_NONE); + } + + /* static */ uint32_t ITuner::Deinitialize() + { + Tuner::Information::Instance().Deinitialize(); + return (Core::ERROR_NONE); + } + + // See if the tuner supports the requested mode, or is configured for the requested mode. This method + // only returns proper values if the Initialize has been called before. + /* static */ bool ITuner::IsSupported(const ITuner::modus mode) + { + return (Tuner::Information::Instance().IsSupported(mode)); + } + + // Accessor to create a tuner. + /* static */ ITuner* ITuner::Create(const string& configuration) + { + return (Tuner::Create(configuration)); + } + +} // namespace Broadcast +} // namespace Thunder diff --git a/Source/extensions/broadcast/MPEGDescriptor.h b/Source/extensions/broadcast/MPEGDescriptor.h new file mode 100644 index 000000000..f416337d7 --- /dev/null +++ b/Source/extensions/broadcast/MPEGDescriptor.h @@ -0,0 +1,180 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MPEGDESCRIPTORS_H +#define __MPEGDESCRIPTORS_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace MPEG { + class EXTERNAL Descriptor { + public: + inline Descriptor() + : _descriptor() + { + } + inline Descriptor(const Core::DataElement& data) + : _descriptor(data) + { + } + inline Descriptor(const Descriptor& copy) + : _descriptor(copy._descriptor) + { + } + inline ~Descriptor() {} + + inline Descriptor& operator=(const Descriptor& RHS) + { + _descriptor = RHS._descriptor; + + return (*this); + } + + public: + inline uint8_t Tag() const { return (_descriptor[0]); } + inline uint8_t Length() const { return (_descriptor[1] + 2); } + inline const uint8_t& operator[](const uint8_t index) const + { + ASSERT(index < _descriptor[1]); + return (_descriptor[2 + index]); + } + + private: + Core::DataElement _descriptor; + }; + + class EXTERNAL DescriptorIterator { + public: + inline DescriptorIterator() + : _descriptors() + , _index(NUMBER_MAX_UNSIGNED(uint32_t)) + { + } + inline DescriptorIterator(const Core::DataElement& data) + : _descriptors(data) + , _index(NUMBER_MAX_UNSIGNED(uint32_t)) + { + } + inline DescriptorIterator(const DescriptorIterator& copy) + : _descriptors(copy._descriptors) + , _index(copy._index) + { + } + inline ~DescriptorIterator() {} + + inline DescriptorIterator& operator=(const DescriptorIterator& rhs) + { + _descriptors = rhs._descriptors; + _index = rhs._index; + + return (*this); + } + + public: + inline bool IsValid() const { return (_index < _descriptors.Size()); } + inline void Reset() { _index = NUMBER_MAX_UNSIGNED(uint32_t); } + bool Next() + { + uint8_t descriptorLength; + + if (_index == NUMBER_MAX_UNSIGNED(uint32_t)) { + _index = 0; + descriptorLength = _descriptors[1] + 2; + } else if (_index < _descriptors.Size()) { + _index += (_descriptors[_index + 1] + 2); + if ((_index + 2) < _descriptors.Size()) { + descriptorLength = _descriptors[_index + 1] + 2; + } + } + + // See if we have a valid descriptor, Does it fit the block we have ? + if ((_index + descriptorLength) > _descriptors.Size()) { + // It's too big, Jump to the end.. + _index = static_cast(_descriptors.Size()); + } + + return (IsValid()); + } + inline Descriptor Current() + { + return (Descriptor(Core::DataElement(_descriptors, _index))); + } + inline const Descriptor Current() const + { + return (Descriptor(Core::DataElement(_descriptors, _index))); + } + bool Tag(const uint8_t tagId) + { + + if (_index == NUMBER_MAX_UNSIGNED(uint32_t)) { + _index = 0; + } + + while (((_index + 2) < _descriptors.Size()) && (_descriptors[_index] != tagId)) { + _index += _descriptors[1] + 2; + } + + // See if we have a valid descriptor, Does it fit the block we have ? + if (((_index + 2) >= _descriptors.Size()) || ((_descriptors[_index + 1] + 2 + _index) > _descriptors.Size())) { + // It's too big, or none was found, jump to the end.. + _index = static_cast(_descriptors.Size()); + } + + return (IsValid()); + } + uint32_t Count() const + { + uint32_t count = 0; + uint32_t offset = 0; + while (offset < _descriptors.Size()) { + count++; + offset += Descriptor(Core::DataElement(_descriptors, offset)).Length(); + } + + if (offset > _descriptors.Size()) { + // reduce the count by one, the last one is toooooooo big + count--; + } + + return (count); + } + + private: + Core::DataElement _descriptors; + uint32_t _index; + }; + + } // namespace MPEG +} // namespace Broadcast +} // namespace Thunder + +#endif //__MPEGDESCRIPTORS_H diff --git a/Source/extensions/broadcast/MPEGSection.h b/Source/extensions/broadcast/MPEGSection.h new file mode 100644 index 000000000..ac6732541 --- /dev/null +++ b/Source/extensions/broadcast/MPEGSection.h @@ -0,0 +1,283 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MPEGSECTION_H +#define __MPEGSECTION_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace MPEG { + class EXTERNAL Section { + public: + inline Section() + : _section() + { + } + inline Section(const Core::DataElement& data) + : _section(data) + { + } + inline Section(const Section& copy) + : _section(copy._section) + { + } + inline ~Section() {} + + inline Section& operator=(const Section& RHS) + { + _section = RHS._section; + + return (*this); + } + + public: + inline bool IsValid() const + { + return ((_section.Size() >= Offset()) && (_section.Size() >= Length()) && (!HasSectionSyntax() || ValidCRC())); + } + inline uint8_t TableId() const { return (_section[0]); } + inline bool HasSectionSyntax() const { return ((_section[1] & 0x80) != 0); } + inline uint16_t Length() const { return (BaseLength() + 3); } + inline uint16_t Extension() const + { + return (HasSectionSyntax() ? ((_section[3] << 8) | _section[4]) : ~0); + } + inline uint8_t Version() const + { + return (HasSectionSyntax() ? ((_section[5] & 0x3E) >> 1) : ~0); + } + inline bool IsCurrent() const + { + return (HasSectionSyntax() ? ((_section[5] & 0x01) != 0) : true); + } + inline bool IsNext() const { return (!IsCurrent()); } + inline uint8_t SectionNumber() const { return (_section[6]); } + inline uint8_t LastSectionNumber() const { return (_section[7]); } + inline uint32_t Hash() const + { + // Extension(16)/TableId(8)/Version(5)/CurNext(1)/SectionIndex(1) + return ((Extension() << 16) | (TableId() << 8) | (Version() << 3) | (IsCurrent() ? 0x04 : 0x00) | (HasSectionSyntax() ? 0x02 : 0x00)); + } + inline Core::DataElement Data() + { + return (Core::DataElement(_section, Offset(), DataLength())); + } + inline const Core::DataElement Data() const + { + return (Core::DataElement(_section, Offset(), DataLength())); + } + template + TYPE GetNumber(const uint16_t offset) const + { + return (_section.GetNumber(offset)); + } + + protected: + inline bool ValidCRC() const + { + uint32_t size = Length() - 4; + uint32_t counterCRC = GetNumber(size); + return (_section.CRC32(0, size) == counterCRC); + } + + private: + inline uint32_t Offset() const { return (HasSectionSyntax() ? 8 : 3); } + inline uint16_t BaseLength() const + { + return ((_section[1] & 0x0F) << 8) | (_section[2]); + } + inline uint16_t DataLength() const + { + return (BaseLength() - (HasSectionSyntax() ? 9 : 0)); + } + + private: + Core::DataElement _section; + }; + + class EXTERNAL Table { + private: + Table() = delete; + Table(const Table&) = delete; + Table& operator=(const Table&) = delete; + + public: + Table(const Core::ProxyType& data) + : _extension(NUMBER_MAX_UNSIGNED(uint16_t)) + , _version(NUMBER_MAX_UNSIGNED(uint8_t)) + , _lastSectionNumber(NUMBER_MAX_UNSIGNED(uint8_t)) + , _tableId(0) + , _sections() + , _data(Core::DataElement(data, 0, 0)) + { + } + ~Table() {} + + public: + inline void Storage(const Core::ProxyType& data) + { + // Drop the current table, load a new storage + _sections.clear(); + _data = Core::DataElement(data); + _lastSectionNumber = NUMBER_MAX_UNSIGNED(uint8_t); + _version = NUMBER_MAX_UNSIGNED(uint8_t); + } + inline bool IsValid() const + { + return ((_sections.size() > 0) && ((_sections.size() - 1) == _lastSectionNumber)); + } + inline uint16_t TableId() const { return (_tableId); } + inline uint16_t Extension() const { return (_extension); } + template + TYPE GetNumber(const uint16_t offset) const + { + return (_data.GetNumber(offset)); + } + inline void Clear() + { + // Drop the current table, load a new storage + _sections.clear(); + _data.Size(0); + _lastSectionNumber = NUMBER_MAX_UNSIGNED(uint8_t); + _version = NUMBER_MAX_UNSIGNED(uint8_t); + } + inline Core::DataElement& Data() { return (_data); } + inline const Core::DataElement& Data() const { return (_data); } + inline bool AddSection(const Section& section) + { + bool addSection = section.IsValid(); + + if (addSection == true) { + if (_sections.size() != 0) { + // Starting something for TableId A and then continue with other + // TableId's Seems to me like a programming error. + if (_tableId != section.TableId()) { + TRACE_L1("Will not add a section, destined for: %d in table: %d", section.TableId(), _tableId); + } + + addSection = (_tableId == section.TableId()); + + if ((addSection == true) && (section.Version() != _version)) { + // Give back all the elemts we do not use.. + _sections.clear(); + _data.Size(0); + _lastSectionNumber = section.LastSectionNumber(); + _version = section.Version(); + + if (_extension != section.Extension()) { + printf("%s, %d -> Interesting the extensions differ\n", + __FUNCTION__, __LINE__); + } + } + } else { + _tableId = section.TableId(); + _data.Size(0); + _lastSectionNumber = section.LastSectionNumber(); + _version = section.Version(); + _extension = section.Extension(); + } + + if (addSection == true) { + uint32_t offset = 0; + uint32_t slotValue = (section.SectionNumber() << 16) | section.Length(); + + std::list::iterator index(_sections.begin()); + + while (index != _sections.end()) { + uint16_t thisLength(*index & 0xFFFF); + uint8_t thisSection((*index >> 16) & 0xFF); + + if (section.SectionNumber() == thisSection) { + // Replace it.. + Insert(section.Data(), thisLength, offset); + *index = slotValue; + break; + } else if (section.SectionNumber() < thisSection) { + // We need to extend the last part + Insert(section.Data(), 0, offset); + index = _sections.insert(index, slotValue); + break; + } + offset += thisLength; + } + + if (index == _sections.end()) { + ASSERT(offset == _data.Size()); + + Insert(section.Data(), 0, offset); + _sections.push_back(slotValue); + } + } + } + + return (addSection); + } + + private: + void Insert(const Core::DataElement& data, const uint16_t allocatedLength, + const uint16_t offset) + { + if (offset < _data.Size()) { + // We need to scrink or extend space... + int32_t needed = (data.Size() - allocatedLength); + uint32_t moveSize = (_data.Size() - offset - allocatedLength); + if (needed > 0) { + // Time to extend + _data.Size(_data.Size() + needed); + ::memmove(&(_data[offset + data.Size()]), + &(_data[offset + allocatedLength]), moveSize); + } else if (needed < 0) { + // time to schrink + ::memmove(&(_data[offset + data.Size()]), + &(_data[offset + allocatedLength]), moveSize); + _data.Size(_data.Size() + needed); + } + } else { + _data.Size(_data.Size() + data.Size()); + } + ::memcpy(&(_data[offset]), data.Buffer(), data.Size()); + } + + private: + uint16_t _extension; + uint8_t _version; + uint8_t _lastSectionNumber; + uint8_t _tableId; + std::list _sections; + Core::DataElement _data; + }; + + } // namespace MPEG +} // namespace Broadcast +} // namespace Thunder + +#endif // __MPEGSECTION_H diff --git a/Source/extensions/broadcast/MPEGTable.h b/Source/extensions/broadcast/MPEGTable.h new file mode 100644 index 000000000..9bc233d0a --- /dev/null +++ b/Source/extensions/broadcast/MPEGTable.h @@ -0,0 +1,281 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MPEGTABLE_H +#define __MPEGTABLE_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace MPEG { + class EXTERNAL PAT { + private: + PAT(const PAT&) = delete; + PAT& operator=(const PAT&) = delete; + + public: + static const uint8_t ID = 0x00; + + public: + class ProgramIterator { + public: + ProgramIterator() + : _index(~0) + , _programs(0) + , _info() + { + } + ProgramIterator(const Core::DataElement& sections) + : _index(~0) + , _programs(sections.Size() / 4) + , _info(sections) + { + } + ProgramIterator(const ProgramIterator& copy) + : _index(copy._index) + , _programs(copy._programs) + , _info(copy._info) + { + } + ~ProgramIterator() {} + + ProgramIterator& operator=(const ProgramIterator& RHS) + { + _info = RHS._info; + _index = RHS._index; + _programs = RHS._programs; + + return (*this); + } + + public: + inline bool IsValid() const { return (_index < _programs); } + inline void Reset() { _index = ~0; } + bool Next() + { + if (_index < _programs) { + _index++; + } else if (_index == static_cast(~0)) { + _index = 0; + } + + return (IsValid()); + } + inline uint16_t ProgramNumber() const + { + return (_info.GetNumber(_index * 4)); + } + inline uint16_t Pid() const + { + return (_info.GetNumber((_index * 4) + 2) & 0x1FFF); + } + uint16_t Count() const { return (_programs); } + + private: + uint32_t _index; + uint32_t _programs; + Core::DataElement _info; + }; + + public: + PAT() + : _data() + , _transportId(~0) + { + } + PAT(const Table& data) + : _data(data.Data()) + , _transportId(data.Extension()) + { + } + ~PAT() {} + + public: + inline uint16_t TransportStreamId() const { return (_transportId); } + inline ProgramIterator Programs() const { return (ProgramIterator(_data)); } + + private: + Core::DataElement _data; + uint16_t _transportId; + }; + + class EXTERNAL PMT { + public: + static const uint16_t ID = 0x02; + + public: + class StreamIterator { + public: + StreamIterator() + : _info() + , _offset(~0) + { + } + StreamIterator(const Core::DataElement& data) + : _info(data) + , _offset(~0) + { + } + StreamIterator(const StreamIterator& copy) + : _info(copy._info) + , _offset(copy._offset) + { + } + ~StreamIterator() {} + + StreamIterator& operator=(const StreamIterator& RHS) + { + _info = RHS._info; + _offset = RHS._offset; + + return (*this); + } + + public: + inline bool IsValid() const { return (_offset < _info.Size()); } + inline void Reset() { _offset = ~0; } + inline bool Next() + { + if (_offset == static_cast(~0)) { + _offset = 0; + } else if (_offset < _info.Size()) { + _offset += (DescriptorSize() + 5); + } + + return (IsValid()); + } + inline uint8_t StreamType() const { return (_info[_offset]); } + inline uint16_t Pid() const + { + return (((_info[_offset + 1] << 8) | _info[_offset + 2]) & 0x1FFF); + } + inline MPEG::DescriptorIterator Descriptors() const + { + return (MPEG::DescriptorIterator( + Core::DataElement(_info, _offset + 5, DescriptorSize()))); + } + inline uint8_t Streams() const + { + uint8_t count = 0; + uint16_t offset = 0; + while (offset < _info.Size()) { + offset += (((_info[offset + 3] << 8) | _info[offset + 4]) & 0x03FF) + 5; + count++; + } + return (count); + } + + private: + inline uint16_t DescriptorSize() const + { + return ((_info[_offset + 3] << 8) | _info[_offset + 4]) & 0x03FF; + } + + private: + Core::DataElement _info; + uint16_t _offset; + }; + + public: + PMT() + : _data() + , _programNumber(~0) + { + } + PMT(const Table& data) + : _data(data.Data()) + , _programNumber(data.Extension()) + { + } + PMT(const uint16_t programId, const Core::DataElement& data) + : _data(data) + , _programNumber(programId) + { + } + PMT(const PMT& copy) + : _data(copy._data) + , _programNumber(copy._programNumber) + { + } + ~PMT() {} + + PMT& operator=(const PMT& rhs) + { + _data = rhs._data; + _programNumber = rhs._programNumber; + return (*this); + } + bool operator==(const PMT& rhs) const + { + return ((_programNumber == rhs._programNumber) && (_data == rhs._data)); + } + bool operator!=(const PMT& rhs) const { return (!operator==(rhs)); } + + public: + inline bool IsValid() const + { + return ((_programNumber != static_cast(~0)) && (_data.Size() >= 2)); + } + inline uint16_t ProgramNumber() const { return (_programNumber); } + uint16_t PCRPid() const + { + return (_data.GetNumber(0) & 0x1FFF); + } + MPEG::DescriptorIterator Descriptors() const + { + uint16_t size(DescriptorSize()); + return (MPEG::DescriptorIterator(Core::DataElement(_data, 4, size))); + } + StreamIterator Streams() const + { + uint16_t offset(DescriptorSize() + 4); + return (StreamIterator( + Core::DataElement(_data, offset, _data.Size() - offset))); + } + + private: + inline uint16_t DescriptorSize() const + { + return (_data.GetNumber(2) & 0x03FF); + } + + private: + Core::DataElement _data; + uint16_t _programNumber; + }; + + } // namespace MPEG +} // namespace Broadcast +} // namespace Thunder + +#endif // __MPEGTABLE_ diff --git a/Source/extensions/broadcast/Module.cpp b/Source/extensions/broadcast/Module.cpp new file mode 100644 index 000000000..393d6a267 --- /dev/null +++ b/Source/extensions/broadcast/Module.cpp @@ -0,0 +1,22 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Source/extensions/broadcast/Module.h b/Source/extensions/broadcast/Module.h new file mode 100644 index 000000000..1fc2eacfd --- /dev/null +++ b/Source/extensions/broadcast/Module.h @@ -0,0 +1,32 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME Broadcast +#endif + +#include + +#if defined(__WINDOWS__) && defined(BROADCAST_EXPORTS) +#undef EXTERNAL +#define EXTERNAL EXTERNAL_EXPORT +#endif + diff --git a/Source/extensions/broadcast/NIT.h b/Source/extensions/broadcast/NIT.h new file mode 100644 index 000000000..5a51d5a9b --- /dev/null +++ b/Source/extensions/broadcast/NIT.h @@ -0,0 +1,192 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DVB_NIT_TABLE_H +#define DVB_NIT_TABLE_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace DVB { + + class EXTERNAL NIT { + public: + static const uint16_t ACTUAL = 0x40; + static const uint16_t OTHER = 0x41; + + public: + class NetworkIterator { + public: + NetworkIterator() + : _info() + , _offset(~0) + { + } + NetworkIterator(const Core::DataElement& data) + : _info(data) + , _offset(~0) + { + } + NetworkIterator(const NetworkIterator& copy) + : _info(copy._info) + , _offset(copy._offset) + { + } + ~NetworkIterator() {} + + NetworkIterator& operator=(const NetworkIterator& RHS) + { + _info = RHS._info; + _offset = RHS._offset; + + return (*this); + } + + public: + inline bool IsValid() const { return (_offset < _info.Size()); } + inline void Reset() { _offset = ~0; } + inline bool Next() + { + if (_offset == static_cast(~0)) { + _offset = 0; + } else if (_offset < _info.Size()) { + _offset += (DescriptorSize() + 6); + } + + return (IsValid()); + } + inline uint16_t TransportStreamId() const + { + return ((_info[_offset + 0] << 8) | _info[_offset + 1]); + } + inline uint16_t OriginalNetworkId() const + { + return ((_info[_offset + 2] << 8) | _info[_offset + 3]); + } + inline MPEG::DescriptorIterator Descriptors() const + { + return (MPEG::DescriptorIterator( + Core::DataElement(_info, _offset + 6, DescriptorSize()))); + } + inline uint8_t Networks() const + { + uint8_t count = 0; + uint16_t offset = 0; + while (offset < _info.Size()) { + offset += (((_info[offset + 4] << 8) | _info[offset + 5]) & 0x0FFF) + 6; + count++; + } + return (count); + } + + private: + inline uint16_t DescriptorSize() const + { + return ((_info[_offset + 4] << 8) | _info[_offset + 5]) & 0x0FFF; + } + + private: + Core::DataElement _info; + uint16_t _offset; + }; + + public: + NIT() + : _data() + , _networkId(~0) + { + } + NIT(const MPEG::Table& data) + : _data(data.Data()) + , _networkId(data.Extension()) + { + } + NIT(const uint16_t networkId, const Core::DataElement& data) + : _data(data) + , _networkId(networkId) + { + } + NIT(const NIT& copy) + : _data(copy._data) + , _networkId(copy._networkId) + { + } + ~NIT() {} + + NIT& operator=(const NIT& rhs) + { + _data = rhs._data; + _networkId = rhs._networkId; + return (*this); + } + bool operator==(const NIT& rhs) const + { + return ((_networkId == rhs._networkId) && (_data == rhs._data)); + } + bool operator!=(const NIT& rhs) const { return (!operator==(rhs)); } + + public: + inline bool IsValid() const + { + return ((_networkId != static_cast(~0)) && (_data.Size() >= 2)); + } + inline uint16_t NetworkId() const { return (_networkId); } + MPEG::DescriptorIterator Descriptors() const + { + uint16_t size(DescriptorSize()); + return (MPEG::DescriptorIterator(Core::DataElement(_data, 4, size))); + } + NetworkIterator Networks() const + { + uint16_t offset = DescriptorSize() + 2; + uint16_t size = (_data.GetNumber(offset) & 0x0FFF); + ASSERT(size == (_data.Size() - offset - 2)); + return (NetworkIterator(Core::DataElement(_data, offset + 2, size))); + } + + private: + inline uint16_t DescriptorSize() const + { + return (_data.GetNumber(2) & 0x0FFF); + } + + private: + Core::DataElement _data; + uint16_t _networkId; + }; + + } // namespace DVB +} // namespace Broadcast +} // namespace Thunder + +#endif // DVB_NIT_TABLE_H diff --git a/Source/extensions/broadcast/Networks.h b/Source/extensions/broadcast/Networks.h new file mode 100644 index 000000000..a49f819a2 --- /dev/null +++ b/Source/extensions/broadcast/Networks.h @@ -0,0 +1,366 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NETWORKS_ADMINISTRATOR_H +#define NETWORKS_ADMINISTRATOR_H + +#include "Definitions.h" +#include "Descriptors.h" +#include "NIT.h" +#include "ProgramTable.h" + +namespace Thunder { + +namespace Broadcast { + + class Networks { + private: + Networks(const Networks&) = delete; + Networks& operator=(const Networks&) = delete; + + class Sink : public ITuner::INotification { + private: + Sink() = delete; + Sink(const Sink&) = delete; + Sink& operator=(const Sink&) = delete; + + public: + Sink(Networks& parent) + : _parent(parent) + { + } + virtual ~Sink() + { + } + + public: + virtual void Activated(ITuner* /* tuner */) override + { + } + virtual void Deactivated(ITuner* tuner) override + { + _parent.Deactivated(tuner); + } + virtual void StateChange(ITuner* tuner) override + { + _parent.StateChange(tuner); + } + + private: + Networks& _parent; + }; + + class Parser : public ISection { + private: + Parser() = delete; + Parser(const Parser&) = delete; + Parser& operator=(const Parser&) = delete; + + public: + Parser(Networks& parent, ITuner* source, const bool scan, const uint16_t pid) + : _parent(parent) + , _source(source) + , _actual(Core::ProxyType::Create(512)) + , _others(Core::ProxyType::Create(512)) + , _pid(pid) + { + if (scan == true) { + Scan(true); + } + } + virtual ~Parser() + { + Scan(false); + } + inline bool operator==(const ITuner* rhs) const + { + return (_source == rhs); + } + inline bool operator!=(const ITuner* rhs) const + { + return (!operator==(rhs)); + } + + public: + void Scan(const bool scan) + { + if (scan == true) { + // Start loading the SDT info + _source->Filter(_pid, DVB::NIT::ACTUAL, this); + _source->Filter(_pid, DVB::NIT::OTHER, this); + } else { + _source->Filter(_pid, DVB::NIT::OTHER, nullptr); + _source->Filter(_pid, DVB::NIT::ACTUAL, nullptr); + } + } + + private: + virtual void Handle(const MPEG::Section& section) override + { + + ASSERT(section.IsValid()); + + if (section.TableId() == DVB::NIT::ACTUAL) { + _actual.AddSection(section); + if (_actual.IsValid() == true) { + _parent.Load(DVB::NIT(_actual)); + } + } else if (section.TableId() == DVB::NIT::OTHER) { + _others.AddSection(section); + if (_others.IsValid() == true) { + _parent.Load(DVB::NIT(_others)); + } + } + } + + private: + Networks& _parent; + ITuner* _source; + MPEG::Table _actual; + MPEG::Table _others; + uint16_t _pid; + }; + + typedef std::list Scanners; + + public: + class Network { + public: + Network() + : _originalNetworkId(~0) + , _transportStreamId(~0) + , _frequency(0) + , _modulation(0) + , _symbolRate(0) + , _name() + { + } + Network(const DVB::NIT::NetworkIterator& info) + : _originalNetworkId(info.OriginalNetworkId()) + , _transportStreamId(info.TransportStreamId()) + , _frequency(0) + , _modulation(0) + , _symbolRate(0) + , _name() + { + MPEG::DescriptorIterator index(info.Descriptors()); + + if (index.Tag(DVB::Descriptors::NetworkName::TAG) == true) { + DVB::Descriptors::NetworkName data(index.Current()); + _name = data.Name(); + } + + // Start searching from the beginning again ! + index.Reset(); + + if (index.Tag(DVB::Descriptors::SatelliteDeliverySystem::TAG) == true) { + DVB::Descriptors::SatelliteDeliverySystem data(index.Current()); + _frequency = data.Frequency(); + _symbolRate = data.SymbolRate(); + _modulation = data.Modulation(); + } else { + // Start searching from the beginning again ! + index.Reset(); + + if (index.Tag(DVB::Descriptors::CableDeliverySystem::TAG) == true) { + DVB::Descriptors::CableDeliverySystem data(index.Current()); + _frequency = data.Frequency(); + _symbolRate = data.SymbolRate(); + _modulation = data.Modulation(); + } + } + } + Network(const Network& copy) + : _originalNetworkId(copy._originalNetworkId) + , _transportStreamId(copy._transportStreamId) + , _frequency(copy._frequency) + , _modulation(copy._modulation) + , _symbolRate(copy._symbolRate) + , _name(copy._name) + { + } + ~Network() + { + } + + Network& operator=(const Network& rhs) + { + _originalNetworkId = rhs._originalNetworkId; + _transportStreamId = rhs._transportStreamId; + _frequency = rhs._frequency; + _modulation = rhs._modulation; + _symbolRate = rhs._symbolRate; + _name = rhs._name; + + return (*this); + } + + public: + bool IsValid() const + { + return (_frequency != 0); + } + inline uint16_t OriginalNetworkId() const + { + return (_originalNetworkId); + } + inline uint16_t TransportStreamId() const + { + return (_transportStreamId); + } + inline uint16_t Frequency() const + { + return (_frequency); + } + inline uint16_t Modulation() const + { + return (_modulation); + } + inline uint16_t SymbolRate() const + { + return (_symbolRate); + } + + private: + uint16_t _originalNetworkId; + uint16_t _transportStreamId; + uint16_t _frequency; + uint16_t _modulation; + uint16_t _symbolRate; + string _name; + }; + + typedef IteratorType Iterator; + + private: + typedef std::map NetworkMap; + + public: + Networks() + : _adminLock() + , _scanners() + , _sink(*this) + , _scan(true) + , _networks() + { + ITuner::Register(&_sink); + } + virtual ~Networks() + { + ITuner::Unregister(&_sink); + } + + public: + void Scan(const bool scan) + { + _adminLock.Lock(); + + if (_scan != scan) { + _scan = scan; + Scanners::iterator index(_scanners.begin()); + while (index != _scanners.end()) { + index->Scan(_scan); + index++; + } + } + _adminLock.Unlock(); + } + Network Id(const uint16_t id) const + { + Network result; + + _adminLock.Lock(); + NetworkMap::const_iterator index(_networks.find(id)); + if (index != _networks.end()) { + result = index->second; + } + _adminLock.Unlock(); + + return (result); + } + Iterator List() const + { + + _adminLock.Lock(); + Iterator value(_networks); + _adminLock.Unlock(); + + return (value); + } + + private: + void Deactivated(ITuner* tuner) + { + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + _scanners.erase(index); + } + + _adminLock.Unlock(); + } + void StateChange(ITuner* tuner) + { + + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + if (tuner->State() == ITuner::IDLE) { + _scanners.erase(index); + } + } else { + if (tuner->State() != ITuner::IDLE) { + uint16_t pid = ProgramTable::Instance().NITPid(tuner->Id()); + if (pid != static_cast(~0)) { + _scanners.emplace_back(*this, tuner, _scan, pid); + } + } + } + + _adminLock.Unlock(); + } + void Load(const DVB::NIT& table) + { + _adminLock.Lock(); + + DVB::NIT::NetworkIterator index = table.Networks(); + + while (index.Next() == true) { + _networks[index.TransportStreamId()] = Network(index); + } + + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + Scanners _scanners; + Sink _sink; + bool _scan; + NetworkMap _networks; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // NETWORKS_ADMINISTRATOR_H diff --git a/Source/extensions/broadcast/ProgramTable.cpp b/Source/extensions/broadcast/ProgramTable.cpp new file mode 100644 index 000000000..bf9b423c7 --- /dev/null +++ b/Source/extensions/broadcast/ProgramTable.cpp @@ -0,0 +1,102 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ProgramTable.h" + +namespace Thunder { + +namespace Broadcast { + + /* static */ Core::ProxyPoolType ProgramTable::_storeFactory(2); + + /* static */ ProgramTable& ProgramTable::Instance() + { + static ProgramTable _instance; + return (_instance); + } + + /* virtual */ void + ProgramTable::Observer::Handle(const MPEG::Section& section) + { + bool completedStep = false; + + if (section.IsValid() == true) { + if (section.TableId() == MPEG::PAT::ID) { + _table.AddSection(section); + + if (_table.IsValid() == true) { + // Iterator over this table and find all program Pids + MPEG::PAT patTable(_table); + MPEG::PAT::ProgramIterator index(patTable.Programs()); + while (index.Next() == true) { + + if (index.ProgramNumber() == 0) { + // ProgramNumber == 0 is reserved for the NIT pid + _parent._nitPids[_keyId] = index.Pid(); + } else { + _entries.push_back(index.Pid() | (index.ProgramNumber() << 16)); + TRACE_L1("ProgramNumber: %d on PID: %d", index.ProgramNumber(), index.Pid()); + } + } + completedStep = true; + } + } else if (section.TableId() == MPEG::PMT::ID) { + _table.AddSection(section); + + if (_table.IsValid() == true) { + + completedStep = true; + + uint32_t key = static_cast(_entries.front() & 0xFFFF) | (_table.Extension() << 16); + + ScanMap::iterator index(_entries.begin()); + + while (index != _entries.end()) { + if (*index == key) { + index = _entries.erase(index); + } else { + completedStep = completedStep && ((*index & 0xFFFF) != (key & 0xFFFF)); + index++; + } + } + + if (_parent.AddProgram(_keyId, _table) == true) { + // do not forget to get a new storage space, the previous one is now + // with the AddProgram !!! + _table.Storage(_storeFactory.Element()); + + TRACE_L1("PMT for %d loaded. PID load for PMTs completed: %s", _table.Extension(), completedStep ? _T("true") : _T("false")); + } + } + } + + if (completedStep == true) { + _table.Clear(); + if (_entries.size() > 0) { + _callback->ChangePid(static_cast(_entries.front() & 0xFFFF), + this); + } else { + _callback->ChangePid(0xFFFF, this); + } + } + } + } + +} // namespace Broadcast +} // namespace Thunder diff --git a/Source/extensions/broadcast/ProgramTable.h b/Source/extensions/broadcast/ProgramTable.h new file mode 100644 index 000000000..f042d1d7d --- /dev/null +++ b/Source/extensions/broadcast/ProgramTable.h @@ -0,0 +1,180 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROGRAMTABLE_H +#define PROGRAMTABLE_H + +#include "Definitions.h" +#include "MPEGTable.h" + +namespace Thunder { + +namespace Broadcast { + + class ProgramTable { + private: + ProgramTable() + : _adminLock() + , _observers() + , _programs() + { + } + ProgramTable(const ProgramTable&) = delete; + ProgramTable& operator=(const ProgramTable&) = delete; + + static Core::ProxyPoolType _storeFactory; + + class Observer : public ISection { + private: + Observer() = delete; + Observer(const Observer&) = delete; + Observer& operator=(const Observer&) = delete; + + typedef std::list ScanMap; + + public: + Observer(ProgramTable* parent, const uint16_t keyId, IMonitor* callback) + : _parent(*parent) + , _callback(callback) + , _keyId(keyId) + , _table(_storeFactory.Element()) + , _entries() + { + } + virtual ~Observer() {} + + public: + virtual void Handle(const MPEG::Section& section) override; + + private: + ProgramTable& _parent; + IMonitor* _callback; + uint16_t _keyId; + MPEG::Table _table; + ScanMap _entries; + }; + + typedef std::map Programs; + typedef std::map Observers; + typedef std::map NITPids; + + public: + static ProgramTable& Instance(); + virtual ~ProgramTable() {} + + public: + inline void Reset() { _programs.clear(); } + ISection* Register(IMonitor* callback, const uint16_t keyId) + { + _adminLock.Lock(); + + ASSERT(_observers.find(callback) == _observers.end()); + + auto index = _observers.emplace( + std::piecewise_construct, std::forward_as_tuple(callback), + std::forward_as_tuple(this, keyId, callback)); + + _adminLock.Unlock(); + + return &(index.first->second); + } + void Unregister(IMonitor* callback) + { + + _adminLock.Lock(); + + Observers::iterator index(_observers.find(callback)); + + if (index != _observers.end()) { + _observers.erase(index); + } + + _adminLock.Unlock(); + } + inline bool Program(const uint16_t keyId, const uint16_t programId, MPEG::PMT& pmt) const + { + bool updated = false; + + _adminLock.Lock(); + + Programs::const_iterator index(_programs.find(Key(keyId, programId))); + if (index != _programs.end()) { + updated = (index->second != pmt); + pmt = index->second; + } + + _adminLock.Unlock(); + + return (updated); + } + uint16_t NITPid(const uint16_t keyId) const + { + uint16_t result(~0); + + NITPids::const_iterator index(_nitPids.find(keyId)); + + if (index != _nitPids.end()) { + result = index->second; + } + + return (result); + } + + private: + inline uint32_t Key(const uint16_t keyId, const uint16_t programId) const + { + uint64_t result = keyId; + return ((result << 16) | programId); + } + bool AddProgram(const uint16_t keyId, const MPEG::Table& data) + { + bool added = false; + uint64_t key(Key(keyId, data.Extension())); + + _adminLock.Lock(); + + Programs::iterator index(_programs.find(key)); + if (index != _programs.end()) { + MPEG::PMT entry(data); + if (index->second != entry) { + added = true; + index->second = entry; + } + } else { + _programs.emplace(std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple(data.Extension(), data.Data())); + added = true; + } + + _adminLock.Unlock(); + + return (added); + } + + private: + mutable Core::CriticalSection _adminLock; + Observers _observers; + Programs _programs; + NITPids _nitPids; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif diff --git a/Source/extensions/broadcast/SDT.h b/Source/extensions/broadcast/SDT.h new file mode 100644 index 000000000..2a1a6506e --- /dev/null +++ b/Source/extensions/broadcast/SDT.h @@ -0,0 +1,203 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DVB_SDT_TABLE_H +#define DVB_SDT_TABLE_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace DVB { + + class EXTERNAL SDT { + public: + static const uint16_t ACTUAL = 0x42; + static const uint16_t OTHER = 0x46; + + public: + enum running { + Undefined = 0, + NotRunning = 1, + AboutToStart = 2, + Pausing = 3, + Running = 4 + }; + + public: + class ServiceIterator { + public: + ServiceIterator() + : _info() + , _offset(~0) + { + } + ServiceIterator(const Core::DataElement& data) + : _info(data) + , _offset(~0) + { + } + ServiceIterator(const ServiceIterator& copy) + : _info(copy._info) + , _offset(copy._offset) + { + } + ~ServiceIterator() {} + + ServiceIterator& operator=(const ServiceIterator& RHS) + { + _info = RHS._info; + _offset = RHS._offset; + + return (*this); + } + + public: + inline bool IsValid() const { return (_offset < _info.Size()); } + inline void Reset() { _offset = ~0; } + inline bool Next() + { + if (_offset == static_cast(~0)) { + _offset = 0; + } else if (_offset < _info.Size()) { + _offset += (DescriptorSize() + 5); + } + + return (IsValid()); + } + inline bool EIT_PF() const + { + return ((_info[_offset + 2] & 0x01) != 0); + } + inline bool EIT_Schedule() const + { + return ((_info[_offset + 2] & 0x02) != 0); + } + inline bool IsFreeToAir() const + { + return ((_info[_offset + 3] & 0x10) != 0); + } + inline running RunningMode() const + { + return (static_cast((_info[_offset + 3] & 0xE0) >> 5)); + } + inline uint16_t ServiceId() const + { + return ((_info[_offset + 0] << 8) | _info[_offset + 1]); + } + inline MPEG::DescriptorIterator Descriptors() const + { + return (MPEG::DescriptorIterator( + Core::DataElement(_info, _offset + 5, DescriptorSize()))); + } + inline uint8_t Services() const + { + uint8_t count = 0; + uint16_t offset = 0; + while (offset < _info.Size()) { + offset += (((_info[offset + 3] << 8) | _info[offset + 4]) & 0x0FFF) + 5; + count++; + } + return (count); + } + + private: + inline uint16_t DescriptorSize() const + { + return ((_info[_offset + 3] << 8) | _info[_offset + 4]) & 0x0FFF; + } + + private: + Core::DataElement _info; + uint16_t _offset; + }; + + public: + SDT() + : _data() + , _transportStreamId(~0) + { + } + SDT(const MPEG::Table& data) + : _data(data.Data()) + , _transportStreamId(data.Extension()) + { + } + SDT(const uint16_t transportStreamId, const Core::DataElement& data) + : _data(data) + , _transportStreamId(transportStreamId) + { + } + SDT(const SDT& copy) + : _data(copy._data) + , _transportStreamId(copy._transportStreamId) + { + } + ~SDT() {} + + SDT& operator=(const SDT& rhs) + { + _data = rhs._data; + _transportStreamId = rhs._transportStreamId; + return (*this); + } + bool operator==(const SDT& rhs) const + { + return ((_transportStreamId == rhs._transportStreamId) && (_data == rhs._data)); + } + bool operator!=(const SDT& rhs) const { return (!operator==(rhs)); } + + public: + inline bool IsValid() const + { + return ((_transportStreamId != static_cast(~0)) && (_data.Size() >= 2)); + } + inline uint16_t TransportStreamId() const { return (_transportStreamId); } + uint16_t OriginalNetworkId() const + { + return (_data.GetNumber(0) & 0x1FFF); + } + ServiceIterator Services() const + { + return (ServiceIterator(Core::DataElement(_data, 3, _data.Size() - 3))); + } + + private: + Core::DataElement _data; + uint16_t _transportStreamId; + }; + + } // namespace DVB +} // namespace Broadcast +} // namespace Thunder + +#endif // DVB_SDT_TABLE_H diff --git a/Source/extensions/broadcast/Schedule.h b/Source/extensions/broadcast/Schedule.h new file mode 100644 index 000000000..935805f9d --- /dev/null +++ b/Source/extensions/broadcast/Schedule.h @@ -0,0 +1,218 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHEDULE_ADMINISTRATOR_H +#define SCHEDULE_ADMINISTRATOR_H + +#include "Definitions.h" +#include "Descriptors.h" +#include "EIT.h" + +namespace Thunder { + +namespace Broadcast { + + class Schedules { + private: + Schedules(const Schedules&) = delete; + Schedules& operator=(const Schedules&) = delete; + + class Sink : public ITuner::INotification { + private: + Sink() = delete; + Sink(const Sink&) = delete; + Sink& operator=(const Sink&) = delete; + + public: + Sink(Schedules& parent) + : _parent(parent) + { + } + virtual ~Sink() + { + } + + public: + virtual void Activated(ITuner* tuner) override + { + } + virtual void Deactivated(ITuner* tuner) override + { + _parent.Deactivated(tuner); + } + virtual void StateChange(ITuner* tuner) override + { + _parent.StateChange(tuner); + } + + private: + Schedules& _parent; + }; + + class Parser : public ISection { + private: + Parser() = delete; + Parser(const Parser&) = delete; + Parser& operator=(const Parser&) = delete; + + public: + Parser(Schedules& parent, ITuner* source, const bool scan) + : _parent(parent) + , _source(source) + , _actual(Core::ProxyType::Create(512)) + , _others(Core::ProxyType::Create(512)) + { + if (scan == true) { + Scan(true); + } + } + virtual ~Parser() + { + Scan(false); + } + inline bool operator==(const ITuner* rhs) const + { + return (_source == rhs); + } + inline bool operator!=(const ITuner* rhs) const + { + return (!operator==(rhs)); + } + + public: + void Scan(const bool scan) + { + if (scan == true) { + // Start loading the SDT info + _source->Filter(0x11, DVB::EIT::ACTUAL, this); + _source->Filter(0x11, DVB::EIT::OTHER, this); + } else { + _source->Filter(0x11, DVB::EIT::OTHER, nullptr); + _source->Filter(0x11, DVB::EIT::ACTUAL, nullptr); + } + } + + private: + virtual void Handle(const MPEG::Section& section) override + { + + ASSERT(section.IsValid()); + + if (section.TableId() == DVB::EIT::ACTUAL) { + _actual.AddSection(section); + if (_actual.IsValid() == true) { + _parent.Load(DVB::EIT(_actual)); + } + } else if (section.TableId() == DVB::EIT::OTHER) { + _others.AddSection(section); + if (_others.IsValid() == true) { + _parent.Load(DVB::EIT(_others)); + } + } + } + + private: + Schedules& _parent; + ITuner* _source; + MPEG::Table _actual; + MPEG::Table _others; + }; + + typedef std::list Scanners; + + public: + Schedules() + : _adminLock() + , _scanners() + , _sink(*this) + , _scan(true) + { + ITuner::Register(&_sink); + } + virtual ~Schedules() + { + ITuner::Unregister(&_sink); + } + + public: + void Scan(const bool scan) + { + _adminLock.Lock(); + + if (_scan != scan) { + _scan = scan; + Scanners::iterator index(_scanners.begin()); + while (index != _scanners.end()) { + index->Scan(_scan); + index++; + } + } + _adminLock.Unlock(); + } + + private: + void Deactivated(ITuner* tuner) + { + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + _scanners.erase(index); + } + + _adminLock.Unlock(); + } + void StateChange(ITuner* tuner) + { + + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + if (tuner->State() == ITuner::IDLE) { + _scanners.erase(index); + } + } else { + if (tuner->State() != ITuner::IDLE) { + _scanners.emplace_back(*this, tuner, _scan); + } + } + + _adminLock.Unlock(); + } + void Load(const DVB::EIT& table) + { + _adminLock.Lock(); + + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + Scanners _scanners; + Sink _sink; + bool _scan; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // SCHEDULE_ADMINISTRATOR_H diff --git a/Source/extensions/broadcast/Services.h b/Source/extensions/broadcast/Services.h new file mode 100644 index 000000000..2dca9434a --- /dev/null +++ b/Source/extensions/broadcast/Services.h @@ -0,0 +1,329 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SERVICE_ADMINISTRATOR_H +#define SERVICE_ADMINISTRATOR_H + +#include "Definitions.h" +#include "Descriptors.h" +#include "SDT.h" + +namespace Thunder { + +namespace Broadcast { + + class Services { + private: + Services(const Services&) = delete; + Services& operator=(const Services&) = delete; + + class Sink : public ITuner::INotification { + private: + Sink() = delete; + Sink(const Sink&) = delete; + Sink& operator=(const Sink&) = delete; + + public: + Sink(Services& parent) + : _parent(parent) + { + } + virtual ~Sink() + { + } + + public: + virtual void Activated(ITuner* /* tuner */) override + { + } + virtual void Deactivated(ITuner* tuner) override + { + _parent.Deactivated(tuner); + } + virtual void StateChange(ITuner* tuner) override + { + _parent.StateChange(tuner); + } + + private: + Services& _parent; + }; + + class Parser : public ISection { + private: + Parser() = delete; + Parser(const Parser&) = delete; + Parser& operator=(const Parser&) = delete; + + public: + Parser(Services& parent, ITuner* source, const bool scan) + : _parent(parent) + , _source(source) + , _actual(Core::ProxyType::Create(512)) + , _others(Core::ProxyType::Create(512)) + { + if (scan == true) { + Scan(true); + } + } + virtual ~Parser() + { + Scan(false); + } + inline bool operator==(const ITuner* rhs) const + { + return (_source == rhs); + } + inline bool operator!=(const ITuner* rhs) const + { + return (!operator==(rhs)); + } + + public: + void Scan(const bool scan) + { + if (scan == true) { + // Start loading the SDT info + _source->Filter(0x11, DVB::SDT::ACTUAL, this); + _source->Filter(0x11, DVB::SDT::OTHER, this); + } else { + _source->Filter(0x11, DVB::SDT::OTHER, nullptr); + _source->Filter(0x11, DVB::SDT::ACTUAL, nullptr); + } + } + + private: + virtual void Handle(const MPEG::Section& section) override + { + + ASSERT(section.IsValid()); + + if (section.TableId() == DVB::SDT::ACTUAL) { + _actual.AddSection(section); + if (_actual.IsValid() == true) { + _parent.Load(DVB::SDT(_actual)); + } + } else if (section.TableId() == DVB::SDT::OTHER) { + _others.AddSection(section); + if (_others.IsValid() == true) { + _parent.Load(DVB::SDT(_others)); + } + } + } + + private: + Services& _parent; + ITuner* _source; + MPEG::Table _actual; + MPEG::Table _others; + }; + + typedef std::list Scanners; + + public: + class Service { + public: + Service() + : _serviceId(~0) + , _info(~0) + , _name() + { + } + Service(const DVB::SDT::ServiceIterator& info) + : _serviceId(info.ServiceId()) + , _info((info.EIT_PF() ? 0x10 : 0x00) | (info.EIT_Schedule() ? 0x20 : 0x00) | (info.IsFreeToAir() ? 0x40 : 0x00) | info.RunningMode()) + , _name(_T("Unknown")) + { + MPEG::DescriptorIterator index(info.Descriptors()); + + if (index.Tag(DVB::Descriptors::Service::TAG) == true) { + DVB::Descriptors::Service nameDescriptor(index.Current()); + _name = nameDescriptor.Name(); + } + } + Service(const Service& copy) + : _serviceId(copy._serviceId) + , _info(copy._info) + , _name(copy._name) + { + } + ~Service() + { + } + + Service& operator=(const Service& rhs) + { + _serviceId = rhs._serviceId; + _info = rhs._info; + _name = rhs._name; + + return (*this); + } + + public: + bool IsValid() const + { + return (_info != static_cast(~0)); + } + const string& Name() const + { + return (_name); + } + inline uint16_t ServiceId() const + { + return (_serviceId); + } + inline bool EIT_PF() const + { + return ((_info & 0x10) != 0); + } + inline bool EIT_Schedule() const + { + return ((_info & 0x20) != 0); + } + inline bool IsFreeToAir() const + { + return ((_info & 0x40) != 0); + } + inline DVB::SDT::running RunningMode() const + { + return (static_cast(_info & 0x07)); + } + + private: + uint16_t _serviceId; + uint8_t _info; + string _name; + }; + + typedef IteratorType Iterator; + + private: + typedef std::map ServiceMap; + + public: + Services() + : _adminLock() + , _scanners() + , _sink(*this) + , _scan(true) + , _services() + { + ITuner::Register(&_sink); + } + virtual ~Services() + { + ITuner::Unregister(&_sink); + } + + public: + void Scan(const bool scan) + { + _adminLock.Lock(); + + if (_scan != scan) { + _scan = scan; + Scanners::iterator index(_scanners.begin()); + while (index != _scanners.end()) { + index->Scan(_scan); + index++; + } + } + _adminLock.Unlock(); + } + Service Id(const uint16_t id) const + { + Service result; + + _adminLock.Lock(); + ServiceMap::const_iterator index(_services.find(id)); + if (index != _services.end()) { + result = index->second; + } + _adminLock.Unlock(); + + return (result); + } + Iterator List() const + { + + _adminLock.Lock(); + Iterator value(_services); + _adminLock.Unlock(); + + return (value); + } + + private: + void Deactivated(ITuner* tuner) + { + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + _scanners.erase(index); + } + + _adminLock.Unlock(); + } + void StateChange(ITuner* tuner) + { + + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + if (tuner->State() == ITuner::IDLE) { + _scanners.erase(index); + } + } else { + if (tuner->State() != ITuner::IDLE) { + _scanners.emplace_back(*this, tuner, _scan); + } + } + + _adminLock.Unlock(); + } + void Load(const DVB::SDT& table) + { + _adminLock.Lock(); + + DVB::SDT::ServiceIterator index = table.Services(); + + while (index.Next() == true) { + _services[index.ServiceId()] = Service(index); + } + + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + Scanners _scanners; + Sink _sink; + bool _scan; + ServiceMap _services; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // SERVICE_ADMINISTRATOR_H diff --git a/Source/extensions/broadcast/TDT.h b/Source/extensions/broadcast/TDT.h new file mode 100644 index 000000000..f8cfb6b9c --- /dev/null +++ b/Source/extensions/broadcast/TDT.h @@ -0,0 +1,140 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DVB_TDT_TABLE_H +#define DVB_TDT_TABLE_H + +// ---- Include system wide include files ---- + +// ---- Include local include files ---- +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "Module.h" + +// ---- Referenced classes and types ---- + +// ---- Helper types and constants ---- + +// ---- Helper functions ---- + +// ---- Class Definition ---- + +namespace Thunder { +namespace Broadcast { + namespace DVB { + + class EXTERNAL TDT { + private: + TDT& operator=(const TDT& rhs) = delete; + + public: + static const uint16_t ID = 0x70; + + public: + TDT() + : _time() + { + } + TDT(const MPEG::Section& data) + : _time() + { + if (data.IsValid() == true) { + const Core::DataElement info(data.Data()); + + // EXAMPLE: 93/10/13 12:45:00 is coded as "0xC079 124500". + uint16_t MJD = (info[0] << 8) | info[1]; + uint32_t J, C, Y, M; + + J = MJD + 2400001 + 68569; + C = 4 * J / 146097; + J = J - (146097 * C + 3) / 4; + Y = 4000 * (J + 1) / 1461001; + J = J - 1461 * Y / 4 + 31; + M = 80 * J / 2447; + + uint8_t day = static_cast(J - 2447 * M / 80); + J = M / 11; + uint8_t month = static_cast(M + 2 - (12 * J)); + uint16_t year = static_cast(100 * (C - 49) + Y + J); + uint8_t uren = ((info[2] >> 4) * 10) + (info[2] & 0xF); + uint8_t minuten = ((info[3] >> 4) * 10) + (info[3] & 0xF); + uint8_t seconden = ((info[4] >> 4) * 10) + (info[4] & 0xF); + + printf("Loadeded: %d-%d-%d %d:%d.%d\n", day, month, year, uren, minuten, seconden); + _time = Core::Time(year, month, day, uren, minuten, seconden, 0, false); + } + } + TDT(const TDT& copy) + : _time(copy._time) + { + } + ~TDT() {} + + public: + inline bool IsValid() const + { + return (_time.IsValid()); + } + inline const Core::Time& Time() const { return (_time); } + + private: + Core::Time _time; + }; + + class EXTERNAL TOT : public TDT { + private: + TOT& operator=(const TOT& rhs) = delete; + + public: + static const uint16_t ID = 0x73; + + public: + TOT() + : TDT() + , _data() + { + } + TOT(const MPEG::Section& data) + : TDT(data) + , _data(data.Data()) + { + } + TOT(const TOT& copy) + : TDT(copy) + , _data(copy._data) + { + } + ~TOT() {} + + public: + MPEG::DescriptorIterator Descriptors() const + { + uint16_t size(((_data[6] & 0xF) << 8) | _data[7]); + return (MPEG::DescriptorIterator(Core::DataElement(_data, 8, size))); + } + + private: + Core::DataElement _data; + }; + + } // namespace DVB +} // namespace Broadcast +} // namespace Thunder + +#endif // DVB_TDT_TABLE_H diff --git a/Source/extensions/broadcast/TimeDate.h b/Source/extensions/broadcast/TimeDate.h new file mode 100644 index 000000000..51c7a31d2 --- /dev/null +++ b/Source/extensions/broadcast/TimeDate.h @@ -0,0 +1,227 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMEDATE_ADMINISTRATOR_H +#define TIMEDATE_ADMINISTRATOR_H + +#include "Definitions.h" +#include "Descriptors.h" +#include "TDT.h" + +namespace Thunder { + +namespace Broadcast { + + class TimeDate { + private: + TimeDate(const TimeDate&) = delete; + TimeDate& operator=(const TimeDate&) = delete; + + class Sink : public ITuner::INotification { + private: + Sink() = delete; + Sink(const Sink&) = delete; + Sink& operator=(const Sink&) = delete; + + public: + Sink(TimeDate& parent) + : _parent(parent) + { + } + virtual ~Sink() + { + } + + public: + virtual void Activated(ITuner* /* tuner */) override + { + } + virtual void Deactivated(ITuner* tuner) override + { + _parent.Deactivated(tuner); + } + virtual void StateChange(ITuner* tuner) override + { + _parent.StateChange(tuner); + } + + private: + TimeDate& _parent; + }; + + class Parser : public ISection { + private: + Parser() = delete; + Parser(const Parser&) = delete; + Parser& operator=(const Parser&) = delete; + + public: + Parser(TimeDate& parent, ITuner* source, const bool scan) + : _parent(parent) + , _source(source) + { + if (scan == true) { + Scan(true); + } + } + virtual ~Parser() + { + Scan(false); + } + inline bool operator==(const ITuner* rhs) const + { + return (_source == rhs); + } + inline bool operator!=(const ITuner* rhs) const + { + return (!operator==(rhs)); + } + + public: + void Scan(const bool scan) + { + if (scan == true) { + // Start loading the SDT info + _source->Filter(0x14, DVB::TDT::ID, this); + _source->Filter(0x14, DVB::TOT::ID, this); + } else { + _source->Filter(0x14, DVB::TOT::ID, nullptr); + _source->Filter(0x14, DVB::TDT::ID, nullptr); + } + } + + private: + virtual void Handle(const MPEG::Section& section) override + { + + ASSERT(section.IsValid()); + + if (section.TableId() == DVB::TDT::ID) { + _parent.Load(DVB::TDT(section)); + } else if (section.TableId() == DVB::TOT::ID) { + _parent.Load(DVB::TOT(section)); + } + } + + private: + TimeDate& _parent; + ITuner* _source; + }; + + typedef std::list Scanners; + + public: + TimeDate() + : _adminLock() + , _scanners() + , _sink(*this) + , _scan(true) + , _time() + { + ITuner::Register(&_sink); + } + virtual ~TimeDate() + { + ITuner::Unregister(&_sink); + } + + public: + void Scan(const bool scan) + { + _adminLock.Lock(); + + if (_scan != scan) { + _scan = scan; + Scanners::iterator index(_scanners.begin()); + while (index != _scanners.end()) { + index->Scan(_scan); + index++; + } + } + _adminLock.Unlock(); + } + + private: + void Deactivated(ITuner* tuner) + { + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + _scanners.erase(index); + } + + _adminLock.Unlock(); + } + void StateChange(ITuner* tuner) + { + + _adminLock.Lock(); + + Scanners::iterator index = std::find(_scanners.begin(), _scanners.end(), tuner); + + if (index != _scanners.end()) { + if (tuner->State() == ITuner::IDLE) { + _scanners.erase(index); + } + } else { + if (tuner->State() != ITuner::IDLE) { + _scanners.emplace_back(*this, tuner, _scan); + } + } + + _adminLock.Unlock(); + } + void Load(const DVB::TOT& table) + { + + _adminLock.Lock(); + + MPEG::DescriptorIterator index = table.Descriptors(); + + while (index.Next() == true) { + } + + _time = table.Time(); + + _adminLock.Unlock(); + } + void Load(const DVB::TDT& table) + { + + _adminLock.Lock(); + + _time = table.Time(); + + _adminLock.Unlock(); + } + + private: + mutable Core::CriticalSection _adminLock; + Scanners _scanners; + Sink _sink; + bool _scan; + Core::Time _time; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // TIMEDATE_ADMINISTRATOR_H diff --git a/Source/extensions/broadcast/TunerAdministrator.cpp b/Source/extensions/broadcast/TunerAdministrator.cpp new file mode 100644 index 000000000..e33ab74bd --- /dev/null +++ b/Source/extensions/broadcast/TunerAdministrator.cpp @@ -0,0 +1,44 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TunerAdministrator.h" + +namespace Thunder { + +namespace Broadcast { + + /* static */ TunerAdministrator& TunerAdministrator::Instance() + { + static TunerAdministrator _instance; + return (_instance); + } + + // Accessor to metadata on the tuners. + /* static */ void ITuner::Register(ITuner::INotification* notify) + { + TunerAdministrator::Instance().Register(notify); + } + + /* static */ void ITuner::Unregister(ITuner::INotification* notify) + { + TunerAdministrator::Instance().Unregister(notify); + } + +} +} // namespace Thunder::Broadcast diff --git a/Source/extensions/broadcast/TunerAdministrator.h b/Source/extensions/broadcast/TunerAdministrator.h new file mode 100644 index 000000000..55a9549f0 --- /dev/null +++ b/Source/extensions/broadcast/TunerAdministrator.h @@ -0,0 +1,192 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TUNER_ADMINISTRATOR_H +#define TUNER_ADMINISTRATOR_H + +#include "Definitions.h" +#include "MPEGTable.h" + +namespace Thunder { + +namespace Broadcast { + + // This class is the linking pin (Singleton) between the Tuner Implementation and the outside world. + // However the definition of this class should not be shared with the outside world as this class + // should not be directly accessed by anyone in the outside world + // The purpose of this class is to allow SI/PSI parsing on tuners while thay are not yet tuned, or + // if they are tuned, to collect (P)SI information on that transport stream and so it looks to the + // outside world that if one tunes automagically the P(SI) get populated. + // So: DO *NOT* USE THIS CLASS OUTSIDE THE BROADCAST LIBRARY, THAT IS WHY IT IS IN ANY OF THE + // INCLUDES OF ANY OTHER HEADER FILES AND ONLY USED BY THE TUNER IMPLEMENTATIONS !!!! + class TunerAdministrator { + public: + struct ICallback { + virtual ~ICallback() {} + + virtual void StateChange(ITuner* tuner) = 0; + }; + + private: + TunerAdministrator(const TunerAdministrator&) = delete; + TunerAdministrator& operator=(const TunerAdministrator&) = delete; + + class Sink : public ICallback { + private: + Sink() = delete; + Sink(const Sink&) = delete; + Sink& operator=(const Sink&) = delete; + + public: + Sink(TunerAdministrator& parent) + : _parent(parent) + { + } + virtual ~Sink() + { + } + + public: + virtual void StateChange(ITuner* tuner) override + { + _parent.StateChange(tuner); + } + + private: + TunerAdministrator& _parent; + }; + + TunerAdministrator() + : _adminLock() + , _sink(*this) + , _observers() + , _tuners() + { + } + + typedef std::list Observers; + typedef std::list Tuners; + + public: + static TunerAdministrator& Instance(); + virtual ~TunerAdministrator() {} + + public: + void Register(ITuner::INotification* observer) + { + _adminLock.Lock(); + + ASSERT(std::find(_observers.begin(), _observers.end(), observer) == _observers.end()); + + _observers.push_back(observer); + + Tuners::iterator index(_tuners.begin()); + while (index != _tuners.end()) { + observer->Activated(*index); + observer->StateChange(*index); + index++; + } + + _adminLock.Unlock(); + } + void Unregister(ITuner::INotification* observer) + { + _adminLock.Lock(); + + Observers::iterator index(std::find(_observers.begin(), _observers.end(), observer)); + + if (index != _observers.end()) { + _observers.erase(index); + } + + _adminLock.Unlock(); + } + ICallback* Announce(ITuner* tuner) + { + _adminLock.Lock(); + + ASSERT(std::find(_tuners.begin(), _tuners.end(), tuner) == _tuners.end()); + + Observers::iterator index(_observers.begin()); + + while (index != _observers.end()) { + (*index)->Activated(tuner); + index++; + } + + _tuners.push_back(tuner); + + _adminLock.Unlock(); + + return (&_sink); + } + void Revoke(ITuner* tuner) + { + _adminLock.Lock(); + + Tuners::iterator entry(std::find(_tuners.begin(), _tuners.end(), tuner)); + + ASSERT(entry != _tuners.end()); + + if (entry != _tuners.end()) { + + Observers::iterator index(_observers.begin()); + + _tuners.erase(entry); + + while (index != _observers.end()) { + (*index)->Deactivated(tuner); + index++; + } + } + + _adminLock.Unlock(); + } + + private: + void StateChange(ITuner* tuner) + { + // Before notifying the others, lets notify the real user of the ITuner interface + tuner->StateChange(); + + _adminLock.Lock(); + + ASSERT(std::find(_tuners.begin(), _tuners.end(), tuner) != _tuners.end()); + + Observers::iterator index(_observers.begin()); + + while (index != _observers.end()) { + (*index)->StateChange(tuner); + index++; + } + + _adminLock.Unlock(); + } + + private: + Core::CriticalSection _adminLock; + Sink _sink; + Observers _observers; + Tuners _tuners; + }; + +} // namespace Broadcast +} // namespace Thunder + +#endif // TUNER_ADMINISTRATOR_H diff --git a/Source/extensions/broadcast/broadcast.h b/Source/extensions/broadcast/broadcast.h new file mode 100644 index 000000000..ca21eb62e --- /dev/null +++ b/Source/extensions/broadcast/broadcast.h @@ -0,0 +1,41 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifndef MODULE_NAME +#error "Please define a MODULE_NAME that describes the binary/library you are building." +#endif + +#include "Definitions.h" +#include "Descriptors.h" +#include "MPEGDescriptor.h" +#include "MPEGSection.h" +#include "MPEGTable.h" +#include "NIT.h" +#include "Networks.h" +#include "ProgramTable.h" +#include "SDT.h" +#include "Services.h" +#include "TDT.h" +#include "TimeDate.h" + +#ifdef __WINDOWS__ +#pragma comment(lib, "broadcast.lib") +#endif diff --git a/Source/extensions/broadcast/cmake/FindNEXUS.cmake b/Source/extensions/broadcast/cmake/FindNEXUS.cmake new file mode 100644 index 000000000..2d1874193 --- /dev/null +++ b/Source/extensions/broadcast/cmake/FindNEXUS.cmake @@ -0,0 +1,83 @@ +# - Try to find Broadcom Nexus. +# Once done this will define +# NEXUS_FOUND - System has Nexus +# NEXUS::NEXUS - The Nexus library +# +# Copyright (C) 2019 Metrological B.V +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +find_path(LIBNEXUS_INCLUDE nexus_config.h + PATH_SUFFIXES refsw) + +find_library(LIBNEXUS_LIBRARY nexus) + +if(EXISTS "${LIBNEXUS_LIBRARY}") + find_library(LIBB_OS_LIBRARY b_os) + find_library(LIBNEXUS_CLIENT_LIBRARY nexus_client) + find_library(LIBNXCLIENT_LIBRARY nxclient) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NEXUS DEFAULT_MSG LIBNEXUS_INCLUDE LIBNEXUS_LIBRARY) + mark_as_advanced(LIBNEXUS_INCLUDE LIBNEXUS_LIBRARY) + + if(NEXUS_FOUND AND NOT TARGET NEXUS::NEXUS) + add_library(NEXUS::NEXUS UNKNOWN IMPORTED) + set_target_properties(NEXUS::NEXUS PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + INTERFACE_INCLUDE_DIRECTORIES "${LIBNEXUS_INCLUDE}" + ) + + if(NOT EXISTS "${LIBNEXUS_CLIENT_LIBRARY}") + message(STATUS "Nexus in Proxy mode") + set_target_properties(NEXUS::NEXUS PROPERTIES + IMPORTED_LOCATION "${LIBNEXUS_LIBRARY}" + ) + else() + message(STATUS "Nexus in Client mode") + set_target_properties(NEXUS::NEXUS PROPERTIES + IMPORTED_LOCATION "${LIBNEXUS_CLIENT_LIBRARY}" + ) + endif() + + if(NOT EXISTS "${LIBNXCLIENT_LIBRARY}") + set_target_properties(NEXUS::NEXUS PROPERTIES + INTERFACE_COMPILE_DEFINITIONS NO_NXCLIENT + ) + endif() + + if(EXISTS "${LIBB_OS_LIBRARY}") + set_target_properties(NEXUS::NEXUS PROPERTIES + IMPORTED_LINK_INTERFACE_LIBRARIES "${LIBB_OS_LIBRARY}" + ) + endif() + endif() + set_target_properties(NEXUS::NEXUS PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "PLATFORM_BRCM" + ) +else() + if(NEXUS_FIND_REQUIRED) + message(FATAL_ERROR "LIBNEXUS_LIBRARY not available") + elseif(NOT NEXUS_FIND_QUIETLY) + message(STATUS "LIBNEXUS_LIBRARY not available") + endif() +endif() \ No newline at end of file diff --git a/Source/extensions/broadcast/cmake/FindNXCLIENT.cmake b/Source/extensions/broadcast/cmake/FindNXCLIENT.cmake new file mode 100644 index 000000000..2e694dbe1 --- /dev/null +++ b/Source/extensions/broadcast/cmake/FindNXCLIENT.cmake @@ -0,0 +1,53 @@ +# - Try to find Broadcom Nexus client. +# Once done this will define +# NXCLIENT_FOUND - System has a Nexus client +# NXCLIENT::NXCLIENT - The Nexus client library +# +# Copyright (C) 2019 Metrological B.V +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +find_path(LIBNXCLIENT_INCLUDE nexus_config.h + PATH_SUFFIXES refsw) + +find_library(LIBNXCLIENT_LIBRARY nxclient) + +if(EXISTS "${LIBNXCLIENT_LIBRARY}") + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NXCLIENT DEFAULT_MSG LIBNXCLIENT_INCLUDE LIBNXCLIENT_LIBRARY) + mark_as_advanced(LIBNXCLIENT_LIBRARY) + + if(NXCLIENT_FOUND AND NOT TARGET NXCLIENT::NXCLIENT) + add_library(NXCLIENT::NXCLIENT UNKNOWN IMPORTED) + set_target_properties(NXCLIENT::NXCLIENT PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBNXCLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBNXCLIENT_INCLUDE}" + ) + endif() +else() + if(NXCLIENT_FIND_REQUIRED) + message(FATAL_ERROR "LIBNXCLIENT_LIBRARY not available") + elseif(NOT NXCLIENT_FIND_QUIETLY) + message(STATUS "LIBNXCLIENT_LIBRARY not available") + endif() +endif() diff --git a/Source/extensions/broadcast/test/BroadcastTester.cpp b/Source/extensions/broadcast/test/BroadcastTester.cpp new file mode 100644 index 000000000..7d72404f8 --- /dev/null +++ b/Source/extensions/broadcast/test/BroadcastTester.cpp @@ -0,0 +1,165 @@ + + /* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define MODULE_NAME BroadcastTest + +#include +#include + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) +using namespace Thunder; + +void printHelp(){ + printf("Keys to use:\n"); + printf("i -> Initialize \n"); + printf("d -> Deinitialize\n"); + printf("c -> Request a tuner\n"); + printf("t -> Tune\n"); + printf("s -> Switch stream\n"); + printf("? -> This message\n"); + printf("q -> Quit\n"); +} + +int main(int /* argc */, const char** /* argv */) +{ + const string configuration = "{ \ + \"frontends\":1, \ + \"decoders\":1, \ + \"standard\":\"DVB\", \ + \"annex\":\"None\", \ + \"scan\":true, \ + \"modus\":\"Terrestrial\" \ + }"; + + const string information = "0"; + + std::list tuners; + std::list::iterator tuner = tuners.begin(); + + class streamInfo { + public: + streamInfo(uint16_t _frequency, Broadcast::Modulation _modulation, uint32_t _symbolRate, uint16_t _fec, Broadcast::SpectralInversion _si) + : frequency(_frequency) + , modulation(_modulation) + , symbolRate(_symbolRate) + , fec(_fec) + , si(_si) + { + } + + const uint16_t frequency; + const Broadcast::Modulation modulation; + const uint32_t symbolRate; + const uint16_t fec; + const Broadcast::SpectralInversion si; + }; + + std::list streams; + + streams.emplace_back(482, Broadcast::QAM64, 8000000, Broadcast::FEC_1_2, Broadcast::Auto); // Digitenne Enschede + streams.emplace_back(642, Broadcast::QAM64, 8000000, Broadcast::FEC_1_2, Broadcast::Auto); // RTON Zorawina + streams.emplace_back(706, Broadcast::QAM64, 8000000, Broadcast::FEC_1_2, Broadcast::Auto); // Digitenne Amersfoort + streams.emplace_back(618, Broadcast::QAM64, 8000000, Broadcast::FEC_1_2, Broadcast::Auto); // Digitenne Amsterdam + streams.emplace_back(474, Broadcast::QAM64, 8000000, Broadcast::FEC_1_2, Broadcast::Auto); // Digitenne Rotterdam + + std::list::iterator stream = streams.begin(); + + char keyPress; + + printf("Ready to start processing events, start with 0 to connect.\n"); + printHelp(); + do { + keyPress = toupper(getchar()); + + switch (keyPress) { + case 'I': { + printf("%s:%d %s Initialize\n", __FILE__, __LINE__, __FUNCTION__); + Broadcast::ITuner::Initialize(configuration); + } break; + + case 'D': { + printf("%s:%d %s Deinitialize\n", __FILE__, __LINE__, __FUNCTION__); + Broadcast::ITuner::Deinitialize(); + } break; + + case 'C': { + printf("%s:%d %s Create tuner\n", __FILE__, __LINE__, __FUNCTION__); + Broadcast::ITuner* newTuner = Broadcast::ITuner::Create(information); + + if (newTuner != nullptr) { + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + std::list::iterator entry(std::find(tuners.begin(), tuners.end(), newTuner)); + if (entry == tuners.end()) { + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + tuners.push_back(newTuner); + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (tuner == tuners.end()) { + tuner = tuners.begin(); + } + } + } else { + printf("Sorry, no more tuners availble!\n"); + } + } break; + + case 'S': { + printf("%s:%d %s Switch tuner\n", __FILE__, __LINE__, __FUNCTION__); + if (++tuner == tuners.end()) { + tuner = tuners.begin(); + } + } break; + + case 'T': { + printf("%s:%d %s Tune\n", __FILE__, __LINE__, __FUNCTION__); + if (*tuner != nullptr) { + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (++stream == streams.end()) { + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + stream = streams.begin(); + } + printf("%s:%d %s Tune: %d %d %d %d %d\n", __FILE__, __LINE__, __FUNCTION__, stream->frequency, stream->modulation, stream->symbolRate, stream->fec, stream->si); + (*tuner)->Tune(stream->frequency, stream->modulation, stream->symbolRate, stream->fec, stream->si); + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + } + } break; + + case 'Q': + break; + + case '?': { + printHelp(); + } break; + + case '\n': + break; + + default: { + printf("Not known. Press '?' for help.\n"); + } break; + } + } while (keyPress != 'Q'); + + // Clear the factory we created.. + Thunder::Core::Singleton::Dispose(); + + printf("\nLeaving the test App !!!\n"); + + return 0; +} diff --git a/Source/extensions/broadcast/test/CMakeLists.txt b/Source/extensions/broadcast/test/CMakeLists.txt new file mode 100644 index 000000000..e495b179f --- /dev/null +++ b/Source/extensions/broadcast/test/CMakeLists.txt @@ -0,0 +1,29 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 Metrological +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include_directories(${CMAKE_CURRENT_LIST_DIR}/../../broadcast) +include_directories($) + +add_executable(BroadcastTester BroadcastTester.cpp) + +target_link_libraries(BroadcastTester + PRIVATE + ${NAMESPACE}Broadcast::${NAMESPACE}Broadcast + ${NAMESPACE}Core::${NAMESPACE}Core +) + +install(TARGETS BroadcastTester DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Test)