├── .envrc ├── .clang-format-ignore ├── Makefile ├── .gitmodules ├── subprojects ├── packagefiles │ ├── darkroot │ │ └── meson.build │ └── logc │ │ ├── meson.options │ │ └── meson.build ├── logc.wrap └── darkroot.wrap ├── src ├── kindlebt_application.c ├── compat_ace.c ├── kindlebt_log.c ├── compat_acealloc.c ├── kindlebt.c ├── compat_ace_shims.c ├── compat_ace_utils.c ├── compat_ace_handler.c ├── kindlebt_utils.c ├── kindlebt_callbacks.c └── compat_ace_implementations.c ├── .clang-format ├── shell.nix ├── meson.options ├── include └── kindlebt │ ├── compat_ace_handler.h │ ├── kindlebt_application.h │ ├── compat_ace.h │ ├── compat_acealloc.h │ ├── compat_ace_utils.h │ ├── kindlebt_log.h │ ├── kindlebt_utils.h │ ├── compat_ace_implementations.h │ ├── compat_ace_shims.h │ ├── kindlebt.h │ ├── kindlebt_defines.h │ └── compat_ace_internals.h ├── .gitignore ├── manual ├── darkroot.md ├── installation.md ├── limitations.md ├── building.md ├── header.html └── DoxygenLayout.xml ├── LICENSE ├── README.md ├── .github └── workflows │ ├── docs.yml │ └── build.yml ├── meson.build ├── Doxyfile └── examples └── basic_usage.c /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | 3 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | ./src/include/ace/* 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format 2 | format: 3 | @clang-format -i -style=file -- src/**.c include/**/*.h examples/**.c 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/doxygen-awesome-css"] 2 | path = vendor/doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | -------------------------------------------------------------------------------- /subprojects/packagefiles/darkroot/meson.build: -------------------------------------------------------------------------------- 1 | project('darkroot', 'c') 2 | 3 | darkroot_inc = include_directories('acs_rtos_reference_ffs/ace/sdk/include') 4 | -------------------------------------------------------------------------------- /subprojects/logc.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/rxi/log.c.git 3 | revision = f9ea34994bd58ed342d2245cd4110bb5c6790153 4 | depth = 1 5 | patch_directory = logc 6 | -------------------------------------------------------------------------------- /subprojects/packagefiles/logc/meson.options: -------------------------------------------------------------------------------- 1 | option( 2 | 'logc_use_color', 3 | type: 'boolean', 4 | value: true, 5 | description: 'Enable color in log.c logs', 6 | ) 7 | -------------------------------------------------------------------------------- /src/kindlebt_application.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bleGattClientCallbacks_t application_gatt_client_callbacks = { 5 | .size = sizeof(bleGattClientCallbacks_t), 6 | }; 7 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: BlockIndent 3 | BreakBeforeBraces: Attach 4 | BreakStringLiterals: 'true' 5 | ColumnLimit: '100' 6 | IndentWidth: '4' 7 | AllowShortIfStatementsOnASingleLine: WithoutElse 8 | PointerAlignment: Left 9 | ... 10 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | pkgs.mkShell { 4 | buildInputs = with pkgs; [ 5 | meson ninja pkg-config python3 6 | bintools patchelf libdwarf 7 | gnutar 8 | clang-tools 9 | gdb unixtools.xxd 10 | doxygen inotify-tools 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | option( 2 | 'force_old_api', 3 | type: 'boolean', 4 | value: false, 5 | description: 'Force use of pre 5.17 APIs', 6 | ) 7 | option( 8 | 'logc_use_color', 9 | type: 'boolean', 10 | value: true, 11 | description: 'Enable color in log.c logs', 12 | yield: true, 13 | ) 14 | -------------------------------------------------------------------------------- /subprojects/darkroot.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = darkroot 3 | lead_directory_missing = yes 4 | source_url = https://dl.espressif.com/dl/acs/acs_rtos_reference_ffs_1.15.0.tar.gz 5 | source_filename = acs.tar.gz 6 | source_hash = e70a82ab242ce9d1412949bb035cd972b785b2627179aa934912fcd751b3b545 7 | patch_directory = darkroot 8 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_HANDLER_H 2 | #define COMPAT_ACE_HANDLER_H 3 | 4 | #include "compat_ace_internals.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void pre5170_gattc_cb_handler(aceAipc_parameter_t* task); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // COMPAT_ACE_HANDLER_H 17 | -------------------------------------------------------------------------------- /include/kindlebt/kindlebt_application.h: -------------------------------------------------------------------------------- 1 | #ifndef KINDLE_BT_APPLICATION_H 2 | #define KINDLE_BT_APPLICATION_H 3 | 4 | #include "kindlebt_defines.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | extern bleGattClientCallbacks_t application_gatt_client_callbacks; 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // KINDLE_BT_APPLICATION_H 17 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_H 2 | #define COMPAT_ACE_H 3 | 4 | #include "compat_ace_internals.h" 5 | #include "compat_ace_shims.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | typedef enum { 12 | PRE_5170 = 1, // < 5.17.0 13 | SINCE_5170 = 2, // >= 5.17.0 14 | } acebt_abi; 15 | 16 | acebt_abi acebt_abi_version(void); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif // COMPAT_ACE_H 23 | -------------------------------------------------------------------------------- /subprojects/packagefiles/logc/meson.build: -------------------------------------------------------------------------------- 1 | project('logc', 'c', meson_version: '>=1.1') 2 | 3 | if get_option('logc_use_color') 4 | add_project_arguments('-DLOG_USE_COLOR', language: 'c') 5 | endif 6 | 7 | logc_inc = include_directories('src') 8 | logc_sources = [ 9 | 'src/log.c', 10 | ] 11 | logc_lib = static_library('logc', logc_sources, include_directories: logc_inc) 12 | 13 | logc_dep = declare_dependency( 14 | include_directories: logc_inc, 15 | link_with: logc_lib, 16 | ) 17 | -------------------------------------------------------------------------------- /include/kindlebt/compat_acealloc.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACEALLOC_H 2 | #define COMPAT_ACEALLOC_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /** 9 | * @brief Free a buffer back 10 | * 11 | * This function free a buffer back with a specified module id and buffer type 12 | * 13 | * @param[in] module_id The ACE module id 14 | * @param[in] buf_type The memory buffer type 15 | * @param[in] p Pointer to the buffer to be freed 16 | */ 17 | // void aceAlloc_free(aceModules_moduleId_t module_id, aceAlloc_bufferType_t buf_type, void* p); 18 | void shadow_aceAlloc_free(void* p); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif // COMPAT_ACEALLOC_H 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Linker files 15 | *.ilk 16 | 17 | # Debugger Files 18 | *.pdb 19 | 20 | # Compiled Dynamic libraries 21 | *.so 22 | *.dylib 23 | *.dll 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | *.lib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | 40 | # debug information files 41 | *.dwo 42 | 43 | # Build dirs 44 | .build*/ 45 | 46 | # Subprojects 47 | subprojects/packagecache/ 48 | subprojects/logc/ 49 | subprojects/darkroot/ 50 | 51 | # Nix 52 | .direnv/ 53 | 54 | # Doxygen docs 55 | doxygen-docs/ 56 | -------------------------------------------------------------------------------- /src/compat_ace.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "log.h" 10 | 11 | // TODO: Might want to turn it into a bitmask instead 12 | acebt_abi acebt_abi_version(void) { 13 | static acebt_abi cached_abi = PRE_5170; 14 | static bool initialized = false; 15 | 16 | if (!initialized) { 17 | void* handle = dlopen(NULL, RTLD_LAZY); 18 | if (handle) { 19 | dlerror(); 20 | void* sym = dlsym(handle, "aceBt_bleRegisterGattClient"); 21 | if (sym) { 22 | cached_abi = SINCE_5170; 23 | } 24 | } else { 25 | log_error("dlopen failed: %s", dlerror()); 26 | } 27 | initialized = true; 28 | } 29 | 30 | return cached_abi; 31 | } 32 | -------------------------------------------------------------------------------- /src/kindlebt_log.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "log.h" 8 | 9 | void kindlebt_set_log_level(log_level_t level) { log_set_level(level); } 10 | 11 | char* append_to_buffer(char* buf, size_t* size, size_t* offset, const char* fmt, ...) { 12 | va_list args; 13 | va_start(args, fmt); 14 | 15 | size_t needed; 16 | char tmp[512]; 17 | needed = vsnprintf(tmp, sizeof(tmp), fmt, args); 18 | 19 | if (*offset + needed >= *size) { 20 | *size = (*offset + needed + 1) * 2; 21 | buf = realloc(buf, *size); 22 | if (!buf) { 23 | va_end(args); 24 | return NULL; 25 | } 26 | } 27 | 28 | snprintf(buf + *offset, *size - *offset, "%s", tmp); 29 | *offset += needed; 30 | 31 | va_end(args); 32 | return buf; 33 | } 34 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_UTILS_H 2 | #define COMPAT_ACE_UTILS_H 3 | 4 | #include 5 | 6 | #include "compat_ace_internals.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | void dump_hex(const void* ptr, size_t size); 13 | void dump_registerCbackGattcData(const registerCbackGattcData_t* data); 14 | void dump_gattc_get_db_data_t(const gattc_get_db_data_t* data); 15 | void dump_aceAipc_parameter_t(aceAipc_parameter_t* task); 16 | void dump_mask_bits(uint16_t mask); 17 | void dump_aipc_handle(aipcHandles_t aipc_handle); 18 | void dump_uuid2(const uuid_t* uuid); 19 | void dump_bleGattRecord_t(const bleGattRecord_t* record); 20 | void dump_bleGattDescriptor_t(const bleGattDescriptor_t* descriptor); 21 | void dump_bleGattCharacteristicsValue_t(const bleGattCharacteristicsValue_t chars_value); 22 | void dump_notify_data_t(const notify_data_t* data); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif // COMPAT_ACE_UTILS_H 29 | -------------------------------------------------------------------------------- /include/kindlebt/kindlebt_log.h: -------------------------------------------------------------------------------- 1 | #ifndef KINDLE_BT_LOG_H 2 | #define KINDLE_BT_LOG_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * @addtogroup KINDLEBT_PUBLIC_API 12 | * @{ 13 | */ 14 | 15 | /** 16 | * @brief Log level for \c log.c 17 | */ 18 | typedef enum { 19 | LOG_LEVEL_TRACE = 0, 20 | LOG_LEVEL_DEBUG, 21 | LOG_LEVEL_INFO, 22 | LOG_LEVEL_WARN, 23 | LOG_LEVEL_ERROR, 24 | LOG_LEVEL_FATAL, 25 | } log_level_t; 26 | 27 | /** 28 | * @brief Set the log level for \c log.c 29 | * 30 | * @remark We use log.c for logging. 31 | * 32 | * If this function is not called, a default \c LOG_LEVEL_TRACE is assumed. 33 | */ 34 | void kindlebt_set_log_level(log_level_t level); 35 | 36 | /** @} */ // KINDLEBT_PUBLIC_API 37 | 38 | char* append_to_buffer(char* buf, size_t* size, size_t* offset, const char* fmt, ...); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif // KINDLE_BT_LOG_H 45 | -------------------------------------------------------------------------------- /src/compat_acealloc.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "log.h" 8 | 9 | // Used in the bleWriteCharacteristics and bleWriteDescriptor calls. 10 | // I don't want to declare it because that would be a whole new linking dependency 11 | 12 | // Defined in ace/ace_modules.h 13 | #define ACE_MODULE_BT 33 14 | // Defined in ace/osal_alloc.h 15 | #define ACE_ALLOC_BUFFER_GENERIC 0 16 | 17 | typedef void (*aceAlloc_free_fn_t)(int, int, void*); 18 | void shadow_aceAlloc_free(void* p) { 19 | static aceAlloc_free_fn_t api = NULL; 20 | static bool initialized = false; 21 | 22 | if (!initialized) { 23 | api = (aceAlloc_free_fn_t)dlsym(RTLD_DEFAULT, "aceAlloc_free"); 24 | initialized = true; 25 | } 26 | 27 | if (api == NULL) { 28 | log_error( 29 | "[%s()]: couldn't match aceAlloc_free symbol. This is not supposed to happen", __func__ 30 | ); 31 | return; 32 | } 33 | 34 | return api(ACE_MODULE_BT, ACE_ALLOC_BUFFER_GENERIC, p); 35 | } 36 | -------------------------------------------------------------------------------- /manual/darkroot.md: -------------------------------------------------------------------------------- 1 | Darkroot {#darkroot} 2 | ===================== 3 | 4 | There are some parts of the `kindlebt` codebase that will remain 5 | undocumented. These parts mainly have to do with existing data structures 6 | used by the underlying Bluetooth library. 7 | 8 | When developing, or debugging `kindlebt`, you might need to reference some of 9 | these undocumented parts. For this, you should go through the **Building 10 | kindlebt** section of the previous [Building][] document. Running 11 | `meson setup` is paramount to this. 12 | 13 | Once you have done so, subprojects will be set up, and then you can find 14 | relevant documentation in the `darkroot` subproject. For instance, using 15 | `ripgrep` to search for a specific enumerator: 16 | 17 | ```sh 18 | rg --color=always -C3 -i 'STATUS_OUT_OF_MEMORY' subprojects/darkroot 19 | ``` 20 | 21 | [Building]: building.md 22 | 23 | 24 |
25 | 26 | | Read Previous | Read Next | 27 | | :------------------------ | --------------------: | 28 | | [Building](building.html) | [Topics](topics.html) | 29 | 30 |
31 | -------------------------------------------------------------------------------- /include/kindlebt/kindlebt_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef KINDLE_BT_UTILS_H 2 | #define KINDLE_BT_UTILS_H 3 | 4 | #include 5 | 6 | #include "kindlebt_defines.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | status_t utilsConvertStrToBdAddr(char* str, bdAddr_t* pAddr); 13 | uint16_t utilsConvertHexStrToByteArray(char* input, uint8_t* output); 14 | 15 | /** 16 | * @addtogroup KINDLEBT_PUBLIC_API 17 | * @{ 18 | */ 19 | 20 | /** 21 | * @brief Find a GATT Characteristic Record by UUID 22 | * 23 | * Even if you know the UUID of a Characteristic, you need to find it in 24 | * @ref pGgatt_service. This is because the library uses these local structs to provide 25 | * and interface for read/writes. 26 | */ 27 | struct aceBT_gattCharRec_t* utilsFindCharRec(uuid_t uuid, uint8_t uuid_len); 28 | 29 | /** @} */ // KINDLEBT_PUBLIC_API 30 | 31 | void setGattBlobFromBytes( 32 | bleGattCharacteristicsValue_t* chars_value, const uint8_t* data, uint16_t size 33 | ); 34 | void freeGattBlob(bleGattCharacteristicsValue_t* chars_value); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #endif // KINDLE_BT_UTILS_H 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sighery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /manual/installation.md: -------------------------------------------------------------------------------- 1 | Installation {#installation} 2 | ============================= 3 | 4 | Head on over to the [Releases tab][releases] and download the relevant file 5 | for your device and firmware version. 6 | 7 | > [!note] 8 | > Right now, only a single HF target is provided, which should be universal 9 | > for any/all devices and HF firmwares, but this might change in the future. 10 | 11 | Once you have downloaded the release, you can unpack it and use 12 | `libkindlebt.so` as any other C library: 13 | 14 | ```sh 15 | # Unpack .tar.gz 16 | tar -xvf kindlebt-hf.tar.gz 17 | 18 | # Either copy to /usr/lib/ or use LD_LIBRARY_PATH 19 | cp libkindlebt.so /usr/lib/. 20 | ./application_that_uses_kindlebt 21 | 22 | # LD_LIBRARY_PATH if you don't want to touch the system 23 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./application_that_uses_kindlebt 24 | ``` 25 | 26 | 27 | [releases]: https://github.com/Sighery/kindlebt/releases 28 | 29 | 30 |
31 | 32 | | Read Previous | Read Next | 33 | | :------------------------------ | ------------------------: | 34 | | [Limitations](limitations.html) | [Building](building.html) | 35 | 36 |
37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kindle Bluetooth for 11th generation and up 2 | 3 | The Bluetooth functionality this project targets is available since the 11th 4 | generation Kindles (KT5, PW5). As of now, this project is just targeting HF 5 | (Hard Float) Kindle firmwares (version 5.16.3 and up). SF (Soft Float) 6 | firmwares are technically possible to support but out of scope for now. 7 | 8 | Since the 11th generation, Amazon seems to have switched to Bluedroid for 9 | their Bluetooth stack (10th gen seems to have used BlueZ). On top of it, they 10 | have their own closed-source Bluetooth stack abstraction `ace_bt`. They have 11 | built their own closed-source server `btmanagerd`, which uses `ace_bt` and 12 | other proprietary libraries. 13 | 14 | There is basically no documentation for `ace_bt` and the different Bluetooth 15 | components on Kindles. That's why this library, `kindlebt`, will be our 16 | wrapper around it, providing a simpler and documented interface that can be 17 | used from applications and from other languages. 18 | 19 | ## Documentation 20 | 21 | For more on installation, usage, limitations, etc, head over to the 22 | [online documentation][manual]. 23 | 24 | 25 | [manual]: https://sighery.github.io/kindlebt/ 26 | 27 | 28 |
29 | 30 | | Read Next | 31 | | -----------------------------------: | 32 | | [Limitations](manual/limitations.md) | 33 | 34 |
35 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace_implementations.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_IMPLEMENTATIONS_H 2 | #define COMPAT_ACE_IMPLEMENTATIONS_H 3 | 4 | #include 5 | 6 | #include "kindlebt_defines.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | status_t pre5170_bleRegisterGattClient( 13 | sessionHandle session_handle, bleGattClientCallbacks_t* callbacks, bleAppId_t app_id 14 | ); 15 | status_t pre5170_bleDeregisterGattClient(sessionHandle session_handle); 16 | status_t pre5170_bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle); 17 | status_t pre5170_bleGetService(bleConnHandle conn_handle); 18 | status_t pre5170_bleReadCharacteristic( 19 | sessionHandle session_handle, bleConnHandle conn_handle, 20 | bleGattCharacteristicsValue_t chars_value 21 | ); 22 | status_t pre5170_bleWriteCharacteristics( 23 | sessionHandle session_handle, bleConnHandle conn_handle, 24 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 25 | ); 26 | status_t pre5170_bleWriteDescriptor( 27 | sessionHandle session_handle, bleConnHandle conn_handle, 28 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 29 | ); 30 | status_t pre5170_bleSetNotification( 31 | sessionHandle session_handle, bleConnHandle conn_handle, 32 | bleGattCharacteristicsValue_t chars_value, bool enable 33 | ); 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif 38 | 39 | #endif // COMPAT_ACE_IMPLEMENTATIONS_H 40 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace_shims.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_SHIMS_H 2 | #define COMPAT_ACE_SHIMS_H 3 | 4 | #include "compat_ace_internals.h" 5 | #include "kindlebt_defines.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | // Shim for the getSessionForCallback/getSessionForTask runtime resolution 12 | sessionHandle getSessionFromHandler(aceAipc_parameter_t* task); 13 | 14 | status_t shim_bleRegisterGattClient( 15 | sessionHandle session_handle, bleGattClientCallbacks_t* callbacks, bleAppId_t app_id 16 | ); 17 | status_t shim_bleDeregisterGattClient(sessionHandle session_handle); 18 | status_t shim_bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle); 19 | status_t shim_bleGetDatabase(bleConnHandle conn_handle); 20 | status_t shim_bleReadCharacteristic( 21 | sessionHandle session_handle, bleConnHandle conn_handle, 22 | bleGattCharacteristicsValue_t chars_value 23 | ); 24 | status_t shim_bleWriteCharacteristic( 25 | sessionHandle session_handle, bleConnHandle conn_handle, 26 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 27 | ); 28 | status_t shim_bleWriteDescriptor( 29 | sessionHandle session_handle, bleConnHandle conn_handle, 30 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 31 | ); 32 | status_t shim_bleSetNotification( 33 | sessionHandle session_handle, bleConnHandle conn_handle, 34 | bleGattCharacteristicsValue_t chars_value, bool enable 35 | ); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif // COMPAT_ACE_SHIMS_H 42 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - 'Doxyfile' 10 | - 'README.md' 11 | - '.github/workflows/docs.yml' 12 | - 'src/**' 13 | - 'include/**' 14 | - 'examples/**' 15 | 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | concurrency: 22 | group: "docs" 23 | cancel-in-progress: false 24 | 25 | env: 26 | DOXYGEN_DOWNLOAD: https://github.com/doxygen/doxygen/releases/download/Release_1_13_2/doxygen-1.13.2.linux.bin.tar.gz 27 | 28 | jobs: 29 | build: 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v5 35 | with: 36 | submodules: true 37 | 38 | - name: Setup Pages 39 | uses: actions/configure-pages@v5 40 | 41 | - name: Install Doxygen 42 | run: | 43 | wget -nv $DOXYGEN_DOWNLOAD 44 | tar -xaf doxygen-1.13.2.linux.bin.tar.gz 45 | cd doxygen-1.13.2/ 46 | sudo make install 47 | 48 | - name: Generate docs 49 | id: build 50 | run: doxygen 51 | 52 | - name: Upload docs artifact 53 | id: upload 54 | uses: actions/upload-pages-artifact@v4 55 | with: 56 | path: doxygen-docs/html/ 57 | 58 | deploy: 59 | runs-on: ubuntu-latest 60 | 61 | environment: 62 | name: github-pages 63 | url: ${{ steps.deployment.outputs.page_url }} 64 | 65 | needs: build 66 | 67 | steps: 68 | - name: Deploy to Github Pages 69 | id: deployment 70 | uses: actions/deploy-pages@v4 71 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'kindlebt', 3 | 'c', 4 | version: 'v0.1.0', 5 | default_options: [ 6 | 'buildtype=debug', 7 | ], 8 | meson_version: '>=1.1' 9 | ) 10 | 11 | add_project_arguments('-fno-omit-frame-pointer', language: 'c') 12 | 13 | if get_option('force_old_api') 14 | add_project_arguments('-DFORCE_OLD_API', language: 'c') 15 | endif 16 | 17 | logc = subproject('logc') 18 | logc_dep = logc.get_variable('logc_dep') 19 | 20 | # Headers dependency 21 | darkroot = subproject('darkroot') 22 | darkroot_inc = darkroot.get_variable('darkroot_inc') 23 | darkroot_dep = declare_dependency(include_directories: darkroot_inc) 24 | 25 | # Actual .so from the Kindle 26 | ace_bt_pc_dep = dependency('ace_bt', method: 'pkg-config') 27 | 28 | library_sources = files( 29 | './src/kindlebt.c', 30 | './src/kindlebt_log.c', 31 | './src/compat_ace.c', 32 | './src/compat_ace_implementations.c', 33 | './src/compat_ace_shims.c', 34 | './src/compat_ace_utils.c', 35 | './src/compat_ace_handler.c', 36 | './src/compat_acealloc.c', 37 | ) 38 | 39 | include_dirs = include_directories( 40 | './include/', 41 | ) 42 | 43 | acebt_dep = declare_dependency( 44 | dependencies: [ace_bt_pc_dep, darkroot_dep], 45 | ) 46 | 47 | # Needed for the acebt ABI introspection 48 | dl_dep = dependency('dl') 49 | 50 | kindlebt = shared_library( 51 | 'kindlebt', 52 | library_sources, 53 | include_directories: include_dirs, 54 | dependencies: [ 55 | acebt_dep, 56 | logc_dep, 57 | dl_dep, 58 | ], 59 | ) 60 | 61 | # Defined in cross file properties 62 | sysroot = meson.get_external_property('sys_root') 63 | 64 | # ACE libraries don't have soname so meson will create hardlinks 65 | cleanup_ace_bt = custom_target( 66 | 'libkindlebt: clean up libace_bt hard link', 67 | depends: kindlebt, 68 | input: [ 69 | join_paths(sysroot, 'usr/lib', 'libace_bt.so'), 70 | kindlebt, 71 | ], 72 | output: 'fake1', 73 | command: ['patchelf', '--replace-needed', '@INPUT0@', 'libace_bt.so', '@INPUT1@'], 74 | build_by_default: true, 75 | ) 76 | -------------------------------------------------------------------------------- /manual/limitations.md: -------------------------------------------------------------------------------- 1 | Limitations {#limitations} 2 | =========================== 3 | 4 | ## Kindle generation 5 | 6 | There are many Kindle devices, as well as generations of some of these product 7 | lines. This Bluetooth library targets 11th generation and later devices: 8 | Kindle 11th generation (**KT5**), Kindle Paperwhite 11th generation (**PW5**), 9 | Kindle Scribe 1st generation (**KS** / **KS1**), etc. 10 | 11 | > [!warning] 12 | > Older devices than KT5 and PW5 **will not work**, as those seem to have been 13 | > based in BlueZ, rather than Bluedroid which is the Bluetooth stack Amazon 14 | > has settled on since the 11th gen release. 15 | 16 | Newer devices will likely still work, but compatibility cannot be confirmed 17 | without manual testing. 18 | 19 | So far, I have tested compatibility in the following devices: 20 | 21 | * Kindle Paperwhite Signature Edition 11th generation (**PW5SE**) 22 | * Kindle Scribe 1st generation (**KS** / **KS1**) 23 | * Kindle Colorsoft Signature Edition 1st generation (**CS** / **CSSE**) 24 | 25 | ## Kindle firmwares 26 | 27 | Kindle devices use an ARM CPU, but there have been changes in the ABI used, 28 | which requires recompiling programs for different targets. Kindle devices used 29 | to use ARM SF (Soft Float). From firmware version 5.16.3, Amazon switched all 30 | Kindles supporting this newer firmware version to ARM HF (Hard Float). 31 | 32 | > [!warning] 33 | > This library will not support, at least for now, Soft Float firmware 34 | > versions. This is because the Bluetooth functionality in Kindles is under 35 | > active development, and older firmwares lack the functionality to make most 36 | > of BLE work. This could be worked around by manually patching `ace_bt` and 37 | > `btmanagerd` with the needed extra support to make BLE work, but it'd be 38 | > quite an undertaking. 39 | 40 | Any firmware from 5.16.3 (taking into account the previously mentioned device 41 | limitations) should work with this library. So far, I have tested 42 | compatibility with the following firmware versions (Bluetooth capabilities 43 | should be the same across all devices past 11th gen, but this assumption is 44 | untested): 45 | 46 | * 5.17.2 (**KS1**) 47 | * 5.18.1.0.1 (**CSSE**) 48 | 49 | ## Implemented functionality 50 | 51 | Only a small subset of the total Bluetooth standard is implemented. You can 52 | see an up to date list of functionality in the 53 | [public API page](@ref KINDLEBT_PUBLIC_API). 54 | 55 | Additionally, there might be some Bluetooth functionality not implemented at 56 | the firmware or hardware level. For instance, I've noticed issues connecting 57 | Bluetooth 4.2 keyboards (HID). There is no easy way to tell what functionality 58 | is missing without trying to use it. 59 | 60 | 61 |
62 | 63 | | Read Previous | Read Next | 64 | | :----------------- | --------------------------------: | 65 | | [Home](index.html) | [Installation](installation.html) | 66 | 67 |
68 | -------------------------------------------------------------------------------- /Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.13.2 2 | 3 | #--------------------------------------------------------------------------- 4 | # Project related configuration options 5 | #--------------------------------------------------------------------------- 6 | 7 | PROJECT_NAME = "kindlebt" 8 | PROJECT_BRIEF = "Bluetooth functionality for Kindle 11th gen and up" 9 | OUTPUT_DIRECTORY = doxygen-docs/ 10 | OPTIMIZE_OUTPUT_FOR_C = YES 11 | INLINE_GROUPED_CLASSES = YES 12 | INLINE_SIMPLE_STRUCTS = YES 13 | TYPEDEF_HIDES_STRUCT = YES 14 | NUM_PROC_THREADS = 0 15 | 16 | #--------------------------------------------------------------------------- 17 | # Build related configuration options 18 | #--------------------------------------------------------------------------- 19 | 20 | HIDE_SCOPE_NAMES = YES 21 | HIDE_COMPOUND_REFERENCE= YES 22 | SHOW_INCLUDE_FILES = NO 23 | LAYOUT_FILE = manual/DoxygenLayout.xml 24 | 25 | #--------------------------------------------------------------------------- 26 | # Configuration options related to the input files 27 | #--------------------------------------------------------------------------- 28 | 29 | INPUT = include \ 30 | src \ 31 | examples \ 32 | README.md \ 33 | manual/limitations.md \ 34 | manual/installation.md \ 35 | manual/building.md \ 36 | manual/darkroot.md 37 | RECURSIVE = YES 38 | EXAMPLE_PATH = examples 39 | EXAMPLE_RECURSIVE = YES 40 | USE_MDFILE_AS_MAINPAGE = README.md 41 | 42 | #--------------------------------------------------------------------------- 43 | # Configuration options related to source browsing 44 | #--------------------------------------------------------------------------- 45 | 46 | SOURCE_BROWSER = YES 47 | INLINE_SOURCES = NO 48 | 49 | #--------------------------------------------------------------------------- 50 | # Configuration options related to the HTML output 51 | #--------------------------------------------------------------------------- 52 | 53 | GENERATE_HTML = YES 54 | HTML_HEADER = manual/header.html 55 | HTML_EXTRA_STYLESHEET = vendor/doxygen-awesome-css/doxygen-awesome.css \ 56 | vendor/doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ 57 | vendor/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css 58 | HTML_EXTRA_FILES = vendor/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ 59 | vendor/doxygen-awesome-css/doxygen-awesome-paragraph-link.js 60 | HTML_COLORSTYLE = LIGHT 61 | DISABLE_INDEX = NO 62 | ENUM_VALUES_PER_LINE = 1 63 | SHOW_ENUM_VALUES = YES 64 | 65 | #--------------------------------------------------------------------------- 66 | # Configuration options related to the LaTeX output 67 | #--------------------------------------------------------------------------- 68 | 69 | GENERATE_LATEX = NO 70 | -------------------------------------------------------------------------------- /manual/building.md: -------------------------------------------------------------------------------- 1 | Building {#building} 2 | ===================== 3 | 4 | This project makes use of [koxtoolchain][], [kindle-sdk][] and [Meson][]. 5 | 6 | ## Getting the SDK 7 | 8 | The SDK is really just a combination of koxtoolchain, which provides a cross 9 | toolchain for a given Kindle system, and a kindle-sdk install on top, which 10 | provides the proprietary C libraries included in a given Kindle device + 11 | firmware version. 12 | 13 | There are basically two choices here, either use a prepackaged release, or 14 | build koxtoolchain + kindle-sdk yourself. 15 | 16 | ### Using a prepackaged SDK 17 | 18 | I build and prepackage a few SDK versions. This includes a packaged 19 | koxtoolchain + kindle-sdk for a given device/firmware version. You can find 20 | these in the [kindle-sdk's releases tab][kindle-sdk releases]. 21 | 22 | Here an example for using the `scribe1-5.17.3` SDK target: 23 | 24 | ```sh 25 | # Download release (you can also download it from your browser) 26 | wget https://github.com/Sighery/kindle-sdk/releases/latest/download/scribe1-5.17.3.tar.gz 27 | 28 | # Unpack release. Any path is fine as long as you keep track of it 29 | tar -xvf scribe1-5.17.3.tar.gz -C ~/ 30 | ``` 31 | 32 | ### Building koxtoolchain/kindle-sdk 33 | 34 | If the kindle-sdk targets I'm building don't work for you, you can build your 35 | own. For that, I recommend looking at the 36 | [`kindle-sdk` README][kindle-sdk README]. 37 | 38 | `koxtoolchain` also provides prebuilt packages that you can just extract. 39 | Unless for some reason you really need to build koxtoolchain, it is 40 | recommended you get the latest from their releases tab and unpack. 41 | koxtoolchain builds will often fail due to flaky network requests, and even 42 | when successful, will take around 30 minutes to complete. 43 | 44 | ## Building kindlebt 45 | 46 | Once you have the SDK set up, you'll need to set up Meson. The steps for your 47 | system will depend, so refer to the [Meson installation][]. If using Nix, the 48 | provided `shell.nix` will do the trick. 49 | 50 | Once you have the system dependencies figured out, we can finally move into 51 | building `kindlebt` with Meson: 52 | 53 | ```sh 54 | # Create the build dir. Any name is fine as long as you keep track of it 55 | mkdir .build_hf 56 | 57 | # Set up the project. Remember where you extracted the SDK to. 58 | # In previous examples it was extracted to just ~/ 59 | meson setup --cross-file ~/x-tools/arm-kindlehf-linux-gnueabihf/meson-crosscompile.txt ./build_hf 60 | 61 | # Compile 62 | meson compile -C .build_hf/ 63 | 64 | # The build targets will be found inside the build dir 65 | ls .build_hf/libkindlebt.so 66 | ``` 67 | 68 | 69 | [koxtoolchain]: https://github.com/koreader/koxtoolchain 70 | [kindle-sdk]: https://github.com/Sighery/kindle-sdk 71 | [Meson]: https://mesonbuild.com 72 | [kindle-sdk releases]: https://github.com/Sighery/kindle-sdk/releases 73 | [kindle-sdk README]: https://github.com/Sighery/kindle-sdk/blob/main/README.md 74 | [Meson installation]: https://mesonbuild.com/Getting-meson.html 75 | 76 | 77 |
78 | 79 | | Read Previous | Read Next | 80 | | :-------------------------------- | ------------------------: | 81 | | [Installation](installation.html) | [Darkroot](darkroot.html) | 82 | 83 |
84 | -------------------------------------------------------------------------------- /manual/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | $treeview 26 | $search 27 | $mathjax 28 | $darkmode 29 | 30 | $extrastylesheet 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 |
46 | 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
57 |
$projectname $projectnumber 58 |
59 |
$projectbrief
60 |
65 |
$projectbrief
66 |
$searchbox
$searchbox
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | env: 12 | PYTHON_VERSION: "3.12" 13 | MESON_VERSION: "1.7.2" 14 | SDK_LATEST: "https://github.com/Sighery/kindle-sdk/releases/latest" 15 | SDK_RELEASE: "https://github.com/Sighery/kindle-sdk/releases/latest/download/" 16 | BUILD_TARGETS: | 17 | [ 18 | { 19 | "name": "kindlebt-hf", 20 | "platform": "arm-kindlehf-linux-gnueabihf", 21 | "sdk": "scribe1-5.17.3.tar.gz" 22 | } 23 | ] 24 | # Not currently supported, but the SF target would be: 25 | # { 26 | # "name": "kindlebt-sf", 27 | # "platform": "arm-kindlepw2-linux-gnueabi", 28 | # "sdk": "kindlepw5-5.16.2.1.1.tar.gz" 29 | # } 30 | 31 | jobs: 32 | build: 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v5 38 | with: 39 | fetch-depth: 0 40 | 41 | - name: Setup Python 42 | uses: actions/setup-python@v6 43 | with: 44 | python-version: "${{ env.PYTHON_VERSION }}" 45 | 46 | - name: Install Meson 47 | run: python -m pip install meson=="${{ env.MESON_VERSION }}" ninja 48 | 49 | - name: Setup env variables 50 | run: | 51 | sdk_release=$(curl -Ls -I -o /dev/null -w %{url_effective} $SDK_LATEST) 52 | echo "SDK_RELEASE_URL=$sdk_release" >> "$GITHUB_ENV" 53 | 54 | - name: Setup SDKs 55 | run: | 56 | echo '${{ env.BUILD_TARGETS }}' | jq -r '"${{ env.SDK_RELEASE }}" + .[].sdk' > sdk-urls.txt 57 | cat sdk-urls.txt | parallel -j8 wget -nv {} 58 | 59 | echo '${{ env.BUILD_TARGETS }}' | jq -c '.[]' | while read target; do 60 | name=$(echo "$target" | jq -r '.name') 61 | sdk=$(echo "$target" | jq -r '.sdk') 62 | 63 | build_dir="sdk-${name}" 64 | 65 | mkdir "$build_dir" 66 | tar -xzf "$sdk" -C "$build_dir" 67 | done 68 | 69 | - name: Configure project 70 | run: | 71 | echo '${{ env.BUILD_TARGETS }}' | jq -c '.[]' | while read target; do 72 | name=$(echo "$target" | jq -r '.name') 73 | platform=$(echo "$target" | jq -r '.platform') 74 | 75 | build_dir=".build_${name}" 76 | 77 | mkdir "$build_dir" 78 | meson setup --wipe --cross-file "./sdk-${name}/x-tools/${platform}/meson-crosscompile.txt" "${build_dir}" 79 | done 80 | 81 | - name: Compile project 82 | run: | 83 | echo '${{ env.BUILD_TARGETS }}' | jq -c '.[]' | while read target; do 84 | name=$(echo "$target" | jq -r '.name') 85 | 86 | build_dir=".build_${name}" 87 | 88 | meson compile -C "${build_dir}/" 89 | done 90 | 91 | - name: Upload meson-log 92 | uses: actions/upload-artifact@v4 93 | if: failure() 94 | with: 95 | name: meson_logs 96 | path: .build_*/meson-logs/meson-log.txt 97 | if-no-files-found: "warn" 98 | 99 | - name: Package binaries 100 | run: | 101 | echo '${{ env.BUILD_TARGETS }}' | jq -c '.[]' | while read target; do 102 | name=$(echo "$target" | jq -r '.name') 103 | 104 | build_dir=".build_${name}" 105 | 106 | tar -czf "${name}.tar.gz" -C "$build_dir" libkindlebt.so kindlebt_test 107 | done 108 | 109 | - name: Release 110 | uses: softprops/action-gh-release@v2 111 | with: 112 | draft: false 113 | body: | 114 | Built with SDK release ${{ env.SDK_RELEASE_URL }} 115 | append_body: true 116 | fail_on_unmatched_files: true 117 | files: "kindlebt-*.tar.gz" 118 | -------------------------------------------------------------------------------- /src/kindlebt.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "log.h" 12 | 13 | #include 14 | #include 15 | 16 | #include "kindlebt_callbacks.c" 17 | 18 | static bleCallbacks_t ble_callbacks = { 19 | .size = sizeof(bleCallbacks_t), 20 | .common_cbs = 21 | { 22 | .size = sizeof(commonCallbacks_t), 23 | .adapter_state_cb = adapterStateCallback, 24 | .bond_state_cb = bondStateCallback, 25 | }, 26 | .ble_registered_cb = bleRegCallback, 27 | .connection_state_change_cb = bleConnStateChangedCallback, 28 | .on_ble_mtu_updated_cb = bleMtuUpdatedCallback, 29 | }; 30 | 31 | static bleGattClientCallbacks_t gatt_client_callbacks = { 32 | .size = sizeof(bleGattClientCallbacks_t), 33 | .on_ble_gattc_service_discovered_cb = bleGattcServiceDiscoveredCallbackWrapper, 34 | .on_ble_gattc_get_gatt_db_cb = bleGattcGetDbCallbackWrapper, 35 | .notify_characteristics_cb = bleGattcNotifyCharsCallbackWrapper, 36 | .on_ble_gattc_read_characteristics_cb = bleGattcReadCharsCallbackWrapper, 37 | .on_ble_gattc_write_characteristics_cb = bleGattcWriteCharsCallbackWrapper, 38 | }; 39 | 40 | bool isBLESupported(void) { return aceBT_isBLESupported(); }; 41 | 42 | status_t enableRadio(sessionHandle session_handle) { return aceBT_enableRadio(session_handle); } 43 | status_t disableRadio(sessionHandle session_handle) { return aceBT_disableRadio(session_handle); } 44 | status_t getRadioState(state_t* p_out_state) { return aceBT_getRadioState(p_out_state); } 45 | 46 | sessionType_t getSupportedSession(void) { return aceBT_getSupportedSession(); } 47 | 48 | status_t openSession(sessionType_t session_type, sessionHandle* session_handle) { 49 | return aceBT_openSession(session_type, NULL, session_handle); 50 | } 51 | 52 | status_t closeSession(sessionHandle session_handle) { return aceBT_closeSession(session_handle); } 53 | 54 | status_t bleRegister(sessionHandle session_handle) { 55 | if (callback_vars.ble_registered) { 56 | log_warn("BLE is already registered\n"); 57 | return ACE_STATUS_OK; 58 | } 59 | 60 | status_t ble_status = aceBT_bleRegister(session_handle, &ble_callbacks); 61 | if (ble_status != ACE_STATUS_OK) return ble_status; 62 | 63 | return waitForCondition( 64 | &callback_vars_lock, &callback_vars_cond, &callback_vars.ble_registered 65 | ); 66 | } 67 | 68 | status_t bleDeregister(sessionHandle session_handle) { 69 | status_t ble_status = aceBT_bleDeRegister(session_handle); 70 | if (ble_status != ACE_STATUS_OK) return ble_status; 71 | 72 | setCallbackVariable( 73 | &callback_vars_lock, &callback_vars_cond, &callback_vars.ble_registered, false 74 | ); 75 | 76 | return ble_status; 77 | } 78 | 79 | status_t bleRegisterGattClient(sessionHandle session_handle, bleGattClientCallbacks_t* callbacks) { 80 | // If provided a callback struct, replace the local copy 81 | // Applications can also just set the application callback directly 82 | if (callbacks != NULL) { 83 | application_gatt_client_callbacks = *callbacks; 84 | } 85 | 86 | return shim_bleRegisterGattClient( 87 | session_handle, &gatt_client_callbacks, ACE_BT_BLE_APPID_GADGETS 88 | ); 89 | } 90 | 91 | status_t bleDeregisterGattClient(sessionHandle session_handle) { 92 | return shim_bleDeregisterGattClient(session_handle); 93 | } 94 | 95 | status_t bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle) { 96 | status_t services_status = shim_bleDiscoverAllServices(session_handle, conn_handle); 97 | if (services_status != ACE_STATUS_OK) return services_status; 98 | 99 | status_t cond_status = 100 | waitForCondition(&callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_discovered); 101 | 102 | return cond_status; 103 | } 104 | 105 | status_t bleGetDatabase(bleConnHandle conn_handle, bleGattsService_t* p_gatt_service) { 106 | status_t db_status = shim_bleGetDatabase(conn_handle); 107 | if (db_status != ACE_STATUS_OK) return db_status; 108 | 109 | status_t cond_status = 110 | waitForCondition(&callback_vars_lock, &callback_vars_cond, &callback_vars.got_gatt_db); 111 | 112 | if (cond_status == ACE_STATUS_OK) p_gatt_service = pGgatt_service; 113 | 114 | return cond_status; 115 | } 116 | 117 | status_t bleCloneGattService( 118 | bleGattsService_t** dst_gatt_service, const bleGattsService_t* src_gatt_service, int no_svc 119 | ) { 120 | return aceBT_bleCloneGattService(dst_gatt_service, src_gatt_service, no_svc); 121 | } 122 | 123 | status_t bleConnect( 124 | sessionHandle session_handle, bleConnHandle* conn_handle, bdAddr_t* p_device, 125 | bleConnParam_t conn_param, bleConnRole_t conn_role, bleConnPriority_t conn_priority 126 | ) { 127 | status_t conn_status = 128 | aceBt_bleConnect(session_handle, p_device, conn_param, conn_role, false, conn_priority); 129 | if (conn_status != ACE_STATUS_OK) return conn_status; 130 | 131 | status_t cond_status = 132 | waitForCondition(&callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_connected); 133 | 134 | if (cond_status == ACE_STATUS_OK) *conn_handle = ble_conn_handle; 135 | 136 | return cond_status; 137 | } 138 | 139 | status_t bleDisconnect(bleConnHandle conn_handle) { return aceBT_bleDisconnect(conn_handle); } 140 | 141 | status_t bleReadCharacteristic( 142 | sessionHandle session_handle, bleConnHandle conn_handle, 143 | bleGattCharacteristicsValue_t chars_value 144 | ) { 145 | return shim_bleReadCharacteristic(session_handle, conn_handle, chars_value); 146 | } 147 | 148 | status_t bleWriteCharacteristic( 149 | sessionHandle session_handle, bleConnHandle conn_handle, 150 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 151 | ) { 152 | return shim_bleWriteCharacteristic(session_handle, conn_handle, chars_value, request_type); 153 | } 154 | 155 | status_t bleWriteDescriptor( 156 | sessionHandle session_handle, bleConnHandle conn_handle, 157 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 158 | ) { 159 | return shim_bleWriteDescriptor(session_handle, conn_handle, chars_value, request_type); 160 | } 161 | 162 | status_t bleSetNotification( 163 | sessionHandle session_handle, bleConnHandle conn_handle, 164 | bleGattCharacteristicsValue_t chars_value, bool enable 165 | ) { 166 | return shim_bleSetNotification(session_handle, conn_handle, chars_value, enable); 167 | } 168 | -------------------------------------------------------------------------------- /include/kindlebt/kindlebt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file kindlebt.h 3 | * @brief Bluetooth library for Kindles. 4 | * 5 | * This will be the entrypoint to the library and where all the Bluetooth functionality 6 | * will be exposed. 7 | * 8 | * As an application, you should generally just need to include this file. 9 | */ 10 | 11 | #ifndef KINDLE_BT_H 12 | #define KINDLE_BT_H 13 | 14 | #include 15 | 16 | #include "kindlebt_defines.h" 17 | #include "kindlebt_utils.h" 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | /** 24 | * @defgroup KINDLEBT_PUBLIC_API Kindlebt Public API 25 | * 26 | * @brief The public API of kindlebt 27 | * 28 | * Kindlebt just implements a small subset of the Bluetooth standard. This autogenerated 29 | * documentation will then serve as a reference of the currently implemented 30 | * functionality. 31 | * 32 | * @remark To use these functions, include @ref kindlebt.h "" 33 | * @par 34 | * @remark For an example usage of connecting to a BLE device, then reading/writing and 35 | * setting notifications on a characteristic, check @ref basic_usage.c 36 | * 37 | * @sa DARKROOT_DS 38 | * 39 | * @{ 40 | */ 41 | 42 | /** 43 | * @brief Is BLE supported by the hardware Bluetooth adapter 44 | */ 45 | bool isBLESupported(void); 46 | 47 | /** 48 | * @brief Turn on the Bluetooth radio. 49 | * 50 | * @note Usually @ref openSession and @ref closeSession will manage it on its own. 51 | * @note Enabling/disabling the radio is an asynchronous operation, so it requires 52 | * waiting for a callback. The @ref status_t response only indicates whether the request 53 | * was made successfully or not. 54 | * 55 | * @warning To receive the operation result, you should register for the 56 | * \c adapter_state_cb callback in @ref commonCallbacks_t 57 | * @sa @ref darkroot 58 | */ 59 | status_t enableRadio(sessionHandle session_handle); 60 | 61 | /** 62 | * @brief Turn off the Bluetooth radio. 63 | * 64 | * @note Usually @ref openSession and @ref closeSession will manage it on its own. 65 | * @note Enabling/disabling the radio is an asynchronous operation, so it requires 66 | * waiting for a callback. The @ref status_t response only indicates whether the request 67 | * was made successfully or not. 68 | * 69 | * @warning To receive the operation result, you should register for the 70 | * \c adapter_state_cb callback in @ref commonCallbacks_t 71 | * @sa @ref darkroot 72 | */ 73 | status_t disableRadio(sessionHandle session_handle); 74 | 75 | /** 76 | * @brief Get current radio status. 77 | * 78 | * @param p_out_state The radio state will be written out to this reference 79 | */ 80 | status_t getRadioState(state_t* p_out_state); 81 | 82 | /** 83 | * @brief Gets the supported Bluetooth session type (Classic, BLE, Dual). 84 | */ 85 | sessionType_t getSupportedSession(void); 86 | 87 | /** 88 | * @brief Open a Bluetooth session. 89 | * 90 | * Necessary and basically the entrypoint of the whole library. 91 | */ 92 | status_t openSession(sessionType_t session_type, sessionHandle* session_handle); 93 | 94 | /** 95 | * @brief Close a Bluetooth session. 96 | */ 97 | status_t closeSession(sessionHandle session_handle); 98 | 99 | /** 100 | * @brief Register as BLE client 101 | * 102 | * Necessary for any BLE operations. 103 | */ 104 | status_t bleRegister(sessionHandle session_handle); 105 | /** 106 | * @brief Deregister as BLE client 107 | */ 108 | status_t bleDeregister(sessionHandle session_handle); 109 | 110 | /** 111 | * @brief Register as a BLE GATT Client 112 | * 113 | * Necessary for any GATT Client operations. 114 | */ 115 | status_t bleRegisterGattClient(sessionHandle session_handle, bleGattClientCallbacks_t* callbacks); 116 | /** 117 | * @brief Deregister as a BLE GATT Client 118 | */ 119 | status_t bleDeregisterGattClient(sessionHandle session_handle); 120 | /** 121 | * @brief Discover all services of a remote GATT Server 122 | * 123 | * Necessary step to discover the GATT Server services. Must be called before 124 | * @ref bleGetDatabase. 125 | */ 126 | status_t bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle); 127 | /** 128 | * @brief Retrieve all services of a remote GATT Server 129 | * 130 | * After discovery (@ref bleDiscoverAllServices) you need to retrieve the services. 131 | * Inside the services you'll also find the available characteristics. Even if you know 132 | * the Characteristic UUID ahead of time, this step is necessary to populate the local 133 | * references. 134 | */ 135 | status_t bleGetDatabase(bleConnHandle conn_handle, bleGattsService_t* p_gatt_service); 136 | 137 | /** 138 | * @brief Connect to a BLE device 139 | */ 140 | status_t bleConnect( 141 | sessionHandle session_handle, bleConnHandle* conn_handle, bdAddr_t* p_device, 142 | bleConnParam_t conn_param, bleConnRole_t conn_role, bleConnPriority_t conn_priority 143 | ); 144 | /** 145 | * @brief Disconnect from a BLE device 146 | */ 147 | status_t bleDisconnect(bleConnHandle conn_handle); 148 | 149 | /** 150 | * @brief Read a Characteristic from a BLE device. 151 | * 152 | * @sa utilsFindCharRec 153 | */ 154 | status_t bleReadCharacteristic( 155 | sessionHandle session_handle, bleConnHandle conn_handle, 156 | bleGattCharacteristicsValue_t chars_value 157 | ); 158 | /** 159 | * @brief Write a Characteristic from a BLE device. 160 | * 161 | * @sa utilsFindCharRec 162 | */ 163 | status_t bleWriteCharacteristic( 164 | sessionHandle session_handle, bleConnHandle conn_handle, 165 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 166 | ); 167 | /** 168 | * @brief Write a Characteristic Descriptor from a BLE device. 169 | * 170 | * @sa utilsFindCharRec 171 | */ 172 | status_t bleWriteDescriptor( 173 | sessionHandle session_handle, bleConnHandle conn_handle, 174 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 175 | ); 176 | /** 177 | * @brief Set notifications on a Characteristic from a BLE device. 178 | * 179 | * @sa utilsFindCharRec 180 | */ 181 | status_t bleSetNotification( 182 | sessionHandle session_handle, bleConnHandle conn_handle, 183 | bleGattCharacteristicsValue_t chars_value, bool enable 184 | ); 185 | 186 | /** @} */ // KINDLEBT_PUBLIC_API 187 | 188 | /** 189 | * @brief [Internal] Clone a GATT Service 190 | * 191 | * @warning As an application you shouldn't need to use this function. It is used by 192 | * @ref bleGetDatabase in the background. 193 | */ 194 | status_t bleCloneGattService( 195 | bleGattsService_t** dst_gatt_service, const bleGattsService_t* src_gatt_service, int no_svc 196 | ); 197 | 198 | /** 199 | * @example basic_usage.c 200 | * This is an example of how to use kindlebt to register as a GATT Client, connect to a 201 | * BLE device, and read, write, and set notifications on a Characteristic that supports 202 | * all these options. 203 | */ 204 | 205 | #ifdef __cplusplus 206 | } 207 | #endif 208 | 209 | #endif // KINDLE_BT_H 210 | -------------------------------------------------------------------------------- /src/compat_ace_shims.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "log.h" 9 | 10 | #include 11 | 12 | /* Internal typedefs matching the two getSessionFor* functions 13 | * getSessionForCallback < 5.17 14 | * getSessionForTask >= 5.17 15 | * Our shim is getSessionFromHandler 16 | */ 17 | typedef sessionHandle (*getSessionForCallback_fn_t)(uint16_t); 18 | typedef sessionHandle (*getSessionForTask_fn_t)(aceAipc_parameter_t*); 19 | 20 | sessionHandle getSessionFromHandler(aceAipc_parameter_t* task) { 21 | static getSessionForCallback_fn_t old_api = NULL; 22 | static getSessionForTask_fn_t new_api = NULL; 23 | static bool initialized = false; 24 | 25 | if (!initialized) { 26 | new_api = (getSessionForTask_fn_t)dlsym(RTLD_DEFAULT, "getSessionForTask"); 27 | if (!new_api) { 28 | old_api = (getSessionForCallback_fn_t)dlsym(RTLD_DEFAULT, "getSessionForCallback"); 29 | } 30 | initialized = true; 31 | } 32 | 33 | if (new_api) { 34 | return new_api(task); 35 | } else if (old_api) { 36 | return old_api(task->callback_id); 37 | } else { 38 | // Nothing matched. We shouldn't reach this 39 | log_error("[%s()]: couldn't match any of the expected getSessionFor* symbols", __func__); 40 | return (sessionHandle)-1; 41 | } 42 | } 43 | 44 | typedef status_t (*aceBt_bleRegisterGattClient_tn_t)( 45 | sessionHandle, bleGattClientCallbacks_t*, bleAppId_t 46 | ); 47 | 48 | status_t shim_bleRegisterGattClient( 49 | sessionHandle session_handle, bleGattClientCallbacks_t* callbacks, bleAppId_t app_id 50 | ) { 51 | static aceBt_bleRegisterGattClient_tn_t new_api = NULL; 52 | 53 | #ifndef FORCE_OLD_API 54 | static bool initialized = false; 55 | 56 | if (!initialized) { 57 | new_api = 58 | (aceBt_bleRegisterGattClient_tn_t)dlsym(RTLD_DEFAULT, "aceBt_bleRegisterGattClient"); 59 | initialized = true; 60 | } 61 | #endif 62 | 63 | if (new_api) { 64 | return new_api(session_handle, callbacks, app_id); 65 | } else { 66 | return pre5170_bleRegisterGattClient(session_handle, callbacks, app_id); 67 | } 68 | } 69 | 70 | typedef status_t (*aceBT_bleDeRegisterGattClient_fn_t)(sessionHandle); 71 | 72 | status_t shim_bleDeregisterGattClient(sessionHandle session_handle) { 73 | static aceBT_bleDeRegisterGattClient_fn_t new_api = NULL; 74 | 75 | #ifndef FORCE_OLD_API 76 | static bool initialized = false; 77 | 78 | if (!initialized) { 79 | new_api = (aceBT_bleDeRegisterGattClient_fn_t 80 | )dlsym(RTLD_DEFAULT, "aceBT_bleDeRegisterGattClient"); 81 | initialized = true; 82 | } 83 | #endif 84 | 85 | if (new_api) { 86 | return new_api(session_handle); 87 | } else { 88 | return pre5170_bleDeregisterGattClient(session_handle); 89 | } 90 | } 91 | 92 | typedef status_t (*aceBT_bleDiscoverAllServices_fn_t)(sessionHandle, bleConnHandle); 93 | 94 | status_t shim_bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle) { 95 | static aceBT_bleDiscoverAllServices_fn_t new_api = NULL; 96 | 97 | #ifndef FORCE_OLD_API 98 | static bool initialized = false; 99 | 100 | if (!initialized) { 101 | new_api = 102 | (aceBT_bleDiscoverAllServices_fn_t)dlsym(RTLD_DEFAULT, "aceBT_bleDiscoverAllServices"); 103 | initialized = true; 104 | } 105 | #endif 106 | 107 | if (new_api) { 108 | return new_api(session_handle, conn_handle); 109 | } else { 110 | return pre5170_bleDiscoverAllServices(session_handle, conn_handle); 111 | } 112 | } 113 | 114 | typedef status_t (*aceBT_bleGetService_fn_t)(bleConnHandle); 115 | 116 | status_t shim_bleGetDatabase(bleConnHandle conn_handle) { 117 | static aceBT_bleGetService_fn_t new_api = NULL; 118 | 119 | #ifndef FORCE_OLD_API 120 | static bool initialized = false; 121 | 122 | if (!initialized) { 123 | new_api = (aceBT_bleGetService_fn_t)dlsym(RTLD_DEFAULT, "aceBT_bleGetService"); 124 | initialized = true; 125 | } 126 | #endif 127 | 128 | if (new_api) { 129 | return new_api(conn_handle); 130 | } else { 131 | return pre5170_bleGetService(conn_handle); 132 | } 133 | } 134 | 135 | typedef status_t (*aceBT_bleReadCharacteristics_fn_t)( 136 | sessionHandle, bleConnHandle, bleGattCharacteristicsValue_t 137 | ); 138 | 139 | status_t shim_bleReadCharacteristic( 140 | sessionHandle session_handle, bleConnHandle conn_handle, 141 | bleGattCharacteristicsValue_t chars_value 142 | ) { 143 | static aceBT_bleReadCharacteristics_fn_t new_api = NULL; 144 | 145 | #ifndef FORCE_OLD_API 146 | static bool initialized = false; 147 | 148 | if (!initialized) { 149 | new_api = 150 | (aceBT_bleReadCharacteristics_fn_t)dlsym(RTLD_DEFAULT, "aceBT_bleReadCharacteristics"); 151 | initialized = true; 152 | } 153 | #endif 154 | 155 | if (new_api) { 156 | return new_api(session_handle, conn_handle, chars_value); 157 | } else { 158 | return pre5170_bleReadCharacteristic(session_handle, conn_handle, chars_value); 159 | } 160 | } 161 | 162 | typedef status_t (*aceBT_bleWriteCharacteristics_fn_t)( 163 | sessionHandle, bleConnHandle, bleGattCharacteristicsValue_t*, responseType_t 164 | ); 165 | 166 | status_t shim_bleWriteCharacteristic( 167 | sessionHandle session_handle, bleConnHandle conn_handle, 168 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 169 | ) { 170 | static aceBT_bleWriteCharacteristics_fn_t new_api = NULL; 171 | 172 | #ifndef FORCE_OLD_API 173 | static bool initialized = false; 174 | 175 | if (!initialized) { 176 | new_api = (aceBT_bleWriteCharacteristics_fn_t 177 | )dlsym(RTLD_DEFAULT, "aceBT_bleWriteCharacteristics"); 178 | initialized = true; 179 | } 180 | #endif 181 | 182 | if (new_api) { 183 | return new_api(session_handle, conn_handle, chars_value, request_type); 184 | } else { 185 | return pre5170_bleWriteCharacteristics( 186 | session_handle, conn_handle, chars_value, request_type 187 | ); 188 | } 189 | } 190 | 191 | typedef status_t (*aceBT_bleWriteDescriptor_fn_t)( 192 | sessionHandle, bleConnHandle, bleGattCharacteristicsValue_t*, responseType_t 193 | ); 194 | 195 | status_t shim_bleWriteDescriptor( 196 | sessionHandle session_handle, bleConnHandle conn_handle, 197 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 198 | ) { 199 | static aceBT_bleWriteDescriptor_fn_t new_api = NULL; 200 | 201 | #ifndef FORCE_OLD_API 202 | static bool initialized = false; 203 | 204 | if (!initialized) { 205 | new_api = 206 | (aceBT_bleWriteCharacteristics_fn_t)dlsym(RTLD_DEFAULT, "aceBT_bleWriteDescriptor"); 207 | } 208 | #endif 209 | 210 | if (new_api) { 211 | return new_api(session_handle, conn_handle, chars_value, request_type); 212 | } else { 213 | return pre5170_bleWriteDescriptor(session_handle, conn_handle, chars_value, request_type); 214 | } 215 | } 216 | 217 | typedef status_t (*aceBT_bleSetNotification_fn_t)( 218 | sessionHandle, bleConnHandle, bleGattCharacteristicsValue_t, bool 219 | ); 220 | 221 | status_t shim_bleSetNotification( 222 | sessionHandle session_handle, bleConnHandle conn_handle, 223 | bleGattCharacteristicsValue_t chars_value, bool enable 224 | ) { 225 | static aceBT_bleSetNotification_fn_t new_api = NULL; 226 | 227 | #ifndef FORCE_OLD_API 228 | static bool initialized = false; 229 | 230 | if (!initialized) { 231 | new_api = (aceBT_bleSetNotification_fn_t)dlsym(RTLD_DEFAULT, "aceBT_bleSetNotification"); 232 | initialized = true; 233 | } 234 | #endif 235 | 236 | if (new_api) { 237 | return new_api(session_handle, conn_handle, chars_value, enable); 238 | } else { 239 | return pre5170_bleSetNotification(session_handle, conn_handle, chars_value, enable); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /include/kindlebt/kindlebt_defines.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file kindlebt_defines.h 3 | * @brief Internal definitions 4 | * 5 | * This file contains some internal structures used in kindlebt as well as some typedefs 6 | * of internal \c darkroot data structures. For more information on some of these types, 7 | * you should get the \c darkroot subproject from the Meson project, and from there you 8 | * can check it out locally and look through it. 9 | * @sa @ref darkroot 10 | */ 11 | 12 | #ifndef KINDLE_BT_DEFINES_H 13 | #define KINDLE_BT_DEFINES_H 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /** 29 | * @defgroup DARKROOT_DS Darkroot Data Structures 30 | * 31 | * @brief darkroot-specific data structures 32 | * 33 | * For more information on some of these types, you should get the \c darkroot subproject 34 | * from the Meson project, and from there you can check it out locally and look through 35 | * it. 36 | * @sa @ref darkroot 37 | * 38 | * @{ 39 | */ 40 | 41 | /** 42 | * @name MAC address 43 | * @{ 44 | */ 45 | /** 46 | * @brief MAC address length 47 | */ 48 | #define MAC_ADDR_LEN ACEBT_MAC_ADDR_LEN 49 | /** 50 | * @brief MAC address string representation length (XX:XX:XX:XX:XX:XX) 51 | */ 52 | #define MAC_ADDR_STR_LEN ACEBT_MAC_ADDR_STR_LEN 53 | /** @} */ 54 | 55 | /** 56 | * @name BLE attribute format 57 | * 58 | * @brief Attribute format used for @ref bleGattAttributeFormat 59 | * 60 | * @{ 61 | */ 62 | /** @brief UINT8 format */ 63 | #define BLE_FORMAT_UINT8 ACEBT_BLE_FORMAT_UINT8 64 | /** @brief UINT16 format */ 65 | #define BLE_FORMAT_UINT16 ACEBT_BLE_FORMAT_UINT16 66 | /** @brief UINT32 format */ 67 | #define BLE_FORMAT_UINT32 ACEBT_BLE_FORMAT_UINT32 68 | /** @brief SINT8 format */ 69 | #define BLE_FORMAT_SINT8 ACEBT_BLE_FORMAT_SINT8 70 | /** @brief SINT16 format */ 71 | #define BLE_FORMAT_SINT16 ACEBT_BLE_FORMAT_SINT16 72 | /** @brief SINT32 format */ 73 | #define BLE_FORMAT_SINT32 ACEBT_BLE_FORMAT_SINT32 74 | /** @brief SFLOAT format */ 75 | #define BLE_FORMAT_SFLOAT ACEBT_BLE_FORMAT_SFLOAT 76 | /** @brief FLOAT format */ 77 | #define BLE_FORMAT_FLOAT ACEBT_BLE_FORMAT_FLOAT 78 | /** @brief BLOB format */ 79 | #define BLE_FORMAT_BLOB ACEBT_BLE_FORMAT_BLOB 80 | /** @} */ 81 | 82 | /** 83 | * @brief Bluetooth radio state 84 | */ 85 | typedef aceBT_state_t state_t; 86 | /** 87 | * @brief Bluetooth API status codes 88 | */ 89 | typedef ace_status_t status_t; 90 | /** 91 | * @brief Bluetooth address 92 | */ 93 | typedef aceBT_bdAddr_t bdAddr_t; 94 | /** 95 | * @brief Bluetooth bond state 96 | */ 97 | typedef aceBT_bondState_t bondState_t; 98 | /** 99 | * @brief Bluetooth UUID types 100 | */ 101 | typedef aceBT_UUIDType_t UUIDType_t; 102 | /** 103 | * @brief Bluetooth UUID struct 104 | * 105 | * The length will depend on the chosen @ref UUIDType_t 106 | */ 107 | typedef aceBT_uuid_t uuid_t; 108 | /** 109 | * @brief Type of write operation for a BLE characteristic 110 | */ 111 | typedef aceBT_responseType_t responseType_t; 112 | 113 | /** 114 | * @brief Session handle for the lifetime of the Bluetooth application 115 | */ 116 | typedef aceBT_sessionHandle sessionHandle; 117 | /** 118 | * @brief Bluetooth session type (Classic, Low Energy, Dual) 119 | */ 120 | typedef aceBT_sessionType_t sessionType_t; 121 | 122 | /** 123 | * @brief Connection handle for the lifetime of a Bluetooth connection 124 | */ 125 | typedef aceBT_bleConnHandle bleConnHandle; 126 | /** 127 | * @brief BLE connection state 128 | */ 129 | typedef aceBT_bleConnState_t bleConnState_t; 130 | /** 131 | * @brief BLE connection parameters 132 | */ 133 | typedef aceBt_bleConnParam_t bleConnParam_t; 134 | /** 135 | * @brief BLE connection role 136 | */ 137 | typedef aceBT_bleConnRole_t bleConnRole_t; 138 | /** 139 | * @brief BLE connection priority 140 | */ 141 | typedef aceBt_bleConnPriority_t bleConnPriority_t; 142 | 143 | /** 144 | * @brief BLE GATT status 145 | */ 146 | typedef aceBT_gattStatus_t gattStatus_t; 147 | /** 148 | * @brief BLE application type 149 | */ 150 | typedef aceBt_bleAppId_t bleAppId_t; 151 | /** 152 | * @brief Structure for a GATT Server service 153 | */ 154 | typedef aceBT_bleGattsService_t bleGattsService_t; 155 | /** 156 | * @brief BLE attribute format 157 | */ 158 | typedef aceBT_bleGattAttributeFormat bleGattAttributeFormat; 159 | /** 160 | * @brief BLE blob value 161 | * 162 | * When reading/writing a characteristic value/descriptor, data will be given through 163 | * this struct. 164 | */ 165 | typedef aceBT_bleGattBlobValue_t bleGattBlobValue_t; 166 | /** 167 | * @brief BLE GATT Characteristic 168 | */ 169 | typedef aceBT_bleGattCharacteristicsValue_t bleGattCharacteristicsValue_t; 170 | /** 171 | * @brief BLE GATT Descriptor 172 | */ 173 | typedef aceBT_bleGattDescriptor_t bleGattDescriptor_t; 174 | /** 175 | * @brief BLE GATT Record (ATT attributes) 176 | */ 177 | typedef aceBT_bleGattRecord_t bleGattRecord_t; 178 | /** 179 | * @brief BLE GATT service type 180 | */ 181 | typedef aceBT_bleGattServiceType_t bleGattServiceType_t; 182 | 183 | /** 184 | * @brief Callback struct of common Classic/LE Bluetooth operations 185 | * 186 | * @remark For a list of available callbacks, search for the original type in 187 | * @ref darkroot 188 | * 189 | * @note Generally no function expects this struct directly, but rather this struct is 190 | * embedded into the BLE/Classic callbacks structs (such as @ref bleCallbacks_t) 191 | */ 192 | typedef aceBT_commonCallbacks_t commonCallbacks_t; 193 | /** 194 | * @brief Callback struct of LE Bluetooth operations 195 | * 196 | * @remark For a list of available callbacks, search for the original type in 197 | * @ref darkroot 198 | */ 199 | typedef aceBT_bleCallbacks_t bleCallbacks_t; 200 | /** 201 | * @brief Callback struct of GATT Client Bluetooth operations 202 | * 203 | * @remark For a list of available callbacks, search for the original type in 204 | * @ref darkroot 205 | */ 206 | typedef aceBT_bleGattClientCallbacks_t bleGattClientCallbacks_t; 207 | 208 | /** @} */ // DARKROOT_DS 209 | 210 | /** 211 | * @internal 212 | * @defgroup KINDLEBT_DS Kindlebt Data Structures 213 | * @brief kindlebt-specific data structures 214 | * 215 | * @warning As an application, you shouldn't need to use or interact with any of these 216 | * structs/variables. These are all for use within the internal kindlebt code to provide 217 | * the API. However, they are documented here since it can help contributors. 218 | * 219 | * @{ 220 | */ 221 | 222 | /** 223 | * @brief Internal struct for certain Bluetooth conditions 224 | * 225 | * Most of the Bluetooth operations are asynchronous, even if the API call returns 226 | * immediately. This is because the API call return just indicates whether the request 227 | * was made successfully or not. To know whether the Bluetooth hardware completed your 228 | * request (or failed to), you'll need to sign up for different callbacks. 229 | * 230 | * For some of these operations, kindlebt will set up its own callbacks and wait for the 231 | * condition, this way we can provide a synchronous behaviour to a few of the API calls. 232 | */ 233 | typedef struct { 234 | bool bt_enabled; 235 | bool ble_registered; 236 | bool mtu_set; 237 | bool gattc_connected; 238 | bool gattc_disconnected; 239 | bool gattc_discovered; 240 | bool got_gatt_db; 241 | } bleCallbackVars_t; 242 | 243 | /** 244 | * @brief Internal @ref bleCallbackVars_t 245 | */ 246 | extern bleCallbackVars_t callback_vars; 247 | /** 248 | * @brief Internal number of GATT services, often paired with @ref pGgatt_service 249 | */ 250 | extern uint32_t gNo_svc; 251 | /** 252 | * @brief Internal reference to a GATT service 253 | */ 254 | extern bleGattsService_t* pGgatt_service; 255 | /** 256 | * @brief Internal BLE connection handle 257 | */ 258 | extern bleConnHandle ble_conn_handle; 259 | 260 | /** @} */ // KINDLEBT_DS 261 | 262 | #ifdef __cplusplus 263 | } 264 | #endif 265 | 266 | #endif // KINDLE_BT_DEFINES_H 267 | -------------------------------------------------------------------------------- /src/compat_ace_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "log.h" 7 | 8 | #include 9 | 10 | void dump_hex(const void* ptr, size_t size) { 11 | const unsigned char* data = (const unsigned char*)ptr; 12 | 13 | // Each byte is "XX ", 3 chars. One '\n' every 16 bytes, and a final '\0' 14 | size_t lines = (size + 15) / 16; 15 | size_t buf_len = (size * 3) + lines + 1; 16 | char* buf = malloc(buf_len); 17 | if (!buf) return; 18 | 19 | char* p = buf; 20 | for (size_t i = 0; i < size; ++i) { 21 | p += snprintf(p, buf + buf_len - p, "%02x ", data[i]); 22 | 23 | // Newline every 16 bytes 24 | if ((i + 1) % 16 == 0) *p++ = '\n'; 25 | } 26 | *p = '\0'; 27 | log_debug("Dumping hex:\n%s", buf); 28 | free(buf); 29 | } 30 | 31 | void dump_registerCbackGattcData(const registerCbackGattcData_t* data) { 32 | if (data == NULL) { 33 | log_warn("Dumping registerCbackGattcData_t: NULL"); 34 | return; 35 | } 36 | 37 | log_debug( 38 | "Dumping registerCbackGattcData_t {\n" 39 | " size: %u\n" 40 | " session_handle: %p\n" 41 | " callback_mask: %u\n" 42 | " is_unregister: %u\n" 43 | " app_id: %d\n" 44 | " out_status: %d\n" 45 | "}", 46 | data->size, data->session_handle, data->callback_mask, data->is_unregister, data->app_id, 47 | data->out_status 48 | ); 49 | } 50 | 51 | void dump_gattc_get_db_data_t(const gattc_get_db_data_t* data) { 52 | if (data == NULL) { 53 | log_warn("Dumping gattc_get_db_data_t: NULL"); 54 | return; 55 | } 56 | 57 | log_debug( 58 | "Dumping gattc_get_db_data_t {\n" 59 | " size: %d\n" 60 | " conn_handle: %p\n" 61 | " uuid: %p\n" 62 | "}", 63 | data->size, data->conn_handle, data->uuid 64 | ); 65 | } 66 | 67 | void dump_aceAipc_parameter_t(aceAipc_parameter_t* task) { 68 | if (task == NULL) { 69 | log_warn("Dumping aceAipc_parameter_t: NULL"); 70 | return; 71 | } 72 | 73 | log_debug( 74 | "Dumping aceAipc_parameter_t {\n" 75 | " reserved0 %u (%p)\n" 76 | " size %u (%p)\n" 77 | " msg_type %u (%p)\n" 78 | " function_id %u (%p)\n" 79 | " cb1 %p\n" 80 | " cb2 %p\n" 81 | " buffer %p\n" 82 | " reserved1 %u (%p)\n" 83 | " reserved2 %u (%p)\n" 84 | " reserved3 %u (%p)\n" 85 | " flags %u (%p)\n" 86 | " reserved4 %u (%p)\n" 87 | " callback_id %u (%p)\n" 88 | " server_id %u (%p)\n" 89 | "}", 90 | task->reserved0, task->reserved0, task->size, task->size, task->msg_type, task->msg_type, 91 | task->function_id, task->function_id, task->cb1, task->cb2, task->buffer, task->reserved1, 92 | task->reserved1, task->reserved2, task->reserved2, task->reserved3, task->reserved3, 93 | task->flags, task->flags, task->reserved4, task->reserved4, task->callback_id, 94 | task->callback_id, task->server_id, task->server_id 95 | ); 96 | } 97 | 98 | void dump_mask_bits(uint16_t mask) { 99 | const int len = 64; 100 | char* buf = malloc(len); 101 | if (!buf) return; 102 | 103 | char* p = buf; 104 | for (int i = 15; i >= 0; --i) { 105 | *p++ = (mask & (1u << i)) ? '1' : '0'; 106 | // Add spacing every 4 bits 107 | if (i % 4 == 0 && i != 0) *p++ = ' '; 108 | } 109 | *p = '\0'; 110 | 111 | log_debug("Dumping mask: 0x%04x (binary: %s)", mask, buf); 112 | free(buf); 113 | } 114 | 115 | void dump_aipc_handle(aipcHandles_t aipc_handle) { 116 | log_debug( 117 | "aipc_handle {\n callback_server_id: %u (0x%04x),\n server_id: %u (0x%04x)\n}", 118 | aipc_handle.callback_server_id, aipc_handle.callback_server_id, aipc_handle.server_id, 119 | aipc_handle.server_id 120 | ); 121 | } 122 | 123 | void dump_uuid2(const uuid_t* uuid) { 124 | log_debug( 125 | "[%s()]: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", 126 | __func__, uuid->uu[0], uuid->uu[1], uuid->uu[2], uuid->uu[3], uuid->uu[4], uuid->uu[5], 127 | uuid->uu[6], uuid->uu[7], uuid->uu[8], uuid->uu[9], uuid->uu[10], uuid->uu[11], 128 | uuid->uu[12], uuid->uu[13], uuid->uu[14], uuid->uu[15] 129 | ); 130 | } 131 | 132 | void dump_bleGattBlobValue_t(const bleGattBlobValue_t* value) { 133 | log_debug("Dumping bleGattBlobValue_t"); 134 | log_debug( 135 | "bleGattBlobValue {\n" 136 | " size %d\n" 137 | " offset %d\n" 138 | " data NEXT\n" 139 | "}", 140 | value->size, value->offset 141 | ); 142 | printf("bleGattBlobValue.data = "); 143 | for (uint16_t i = 0; i < value->size; ++i) { 144 | printf("0x%02X ", value->data[i]); 145 | } 146 | printf("\n"); 147 | } 148 | 149 | void dump_bleGattRecord_t(const bleGattRecord_t* record) { 150 | log_debug("Dumping bleGattRecord_t"); 151 | dump_uuid2(&record->uuid); 152 | log_debug( 153 | "bleGattRecord_t {\n" 154 | " uuid PREV\n" 155 | " attProp %d\n" 156 | " attPerm %d\n" 157 | " handle %d (%p)\n" 158 | "}", 159 | record->attProp, record->attPerm, record->handle, record->handle 160 | ); 161 | } 162 | 163 | void dump_bleGattDescriptor_t(const bleGattDescriptor_t* descriptor) { 164 | if (descriptor == NULL) { 165 | log_debug("Dumping bleGattDescriptor_t: NULL"); 166 | return; 167 | } 168 | 169 | log_debug("Dumping bleGattDescriptor_t"); 170 | log_debug( 171 | "bleGattDescriptor_t {\n" 172 | " gattRecord NEXT\n" 173 | " blobValue NEXT\n" 174 | " is_set %d\n" 175 | " is_notify %d\n" 176 | " desc_auth_retry %d\n" 177 | " write_type %d\n" 178 | "}", 179 | descriptor->is_set, descriptor->is_notify, descriptor->desc_auth_retry, 180 | descriptor->write_type 181 | ); 182 | 183 | dump_bleGattRecord_t(&descriptor->gattRecord); 184 | dump_bleGattBlobValue_t(&descriptor->blobValue); 185 | } 186 | 187 | void dump_bleGattCharacteristicsValue_t(const bleGattCharacteristicsValue_t chars_value) { 188 | log_debug("Dumping bleGattCharacteristicsValue_t"); 189 | log_debug( 190 | "bleGattCharacteristicsValue_t {\n" 191 | " format %d\n" 192 | " gattRecord NEXT\n" 193 | " gattDescriptor NEXT\n" 194 | " auth_retry %d\n" 195 | " read_auth_retry %d\n" 196 | " write_type %d\n" 197 | " descList NEXT\n" 198 | " multiDescCount %d\n" 199 | "}", 200 | chars_value.format, chars_value.auth_retry, chars_value.read_auth_retry, 201 | chars_value.write_type, chars_value.multiDescCount 202 | ); 203 | 204 | if (chars_value.format == 255) { 205 | dump_bleGattBlobValue_t(&chars_value.blobValue); 206 | } 207 | 208 | dump_bleGattRecord_t(&chars_value.gattRecord); 209 | log_debug("Main descriptor:"); 210 | dump_bleGattDescriptor_t(&chars_value.gattDescriptor); 211 | 212 | log_debug("Linked list descriptors:"); 213 | if (chars_value.multiDescCount) { 214 | struct aceBT_gattDescRec_t* desc_rec = NULL; 215 | STAILQ_FOREACH(desc_rec, &chars_value.descList, link) { 216 | dump_bleGattDescriptor_t(&desc_rec->value); 217 | } 218 | } 219 | log_debug("Done linked list descriptors"); 220 | } 221 | 222 | void dump_notify_data_t(const notify_data_t* data) { 223 | log_debug("Dumping notify_data_t"); 224 | log_debug( 225 | "notify_data_t {\n" 226 | " size %d\n" 227 | " conn_handle %d (%p)\n" 228 | " session_handle %d (%p)\n" 229 | // " confirm %d\n" 230 | " value NEXT\n" 231 | " data_len %d\n" 232 | " data %p SKIPPED\n" 233 | "}", 234 | data->size, data->conn_handle, data->conn_handle, data->session_handle, 235 | data->session_handle, 236 | // data->confirm, 237 | data->data_len, data->data 238 | ); 239 | 240 | dump_bleGattCharacteristicsValue_t(data->value); 241 | } 242 | -------------------------------------------------------------------------------- /src/compat_ace_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "log.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | void pre5170_gattc_cb_handler(aceAipc_parameter_t* task) { 10 | log_debug("Called into pre 5.17 %s", __func__); 11 | 12 | if (task == NULL) { 13 | log_info("[%s()]: Server handler callback: data is null", __func__); 14 | return; 15 | } 16 | 17 | printf("Dumping aceAipc_parameter_t memory:\n"); 18 | dump_hex(task, 128); 19 | dump_aceAipc_parameter_t(task); 20 | 21 | /* In AIPC callback this runs in server side callback context. Hence use the 22 | callback id to retrive the session info*/ 23 | sessionHandle session_handle = getSessionFromHandler(task); 24 | bleGattClientCallbacks_t* p_client_callbacks = 25 | getBTClientData(session_handle, CALLBACK_INDEX_BLE_GATT_CLIENT); 26 | if (session_handle == NULL || p_client_callbacks == NULL) { 27 | log_error( 28 | "[%s()]: Error invalid handle, session %p callback %p", __func__, session_handle, 29 | p_client_callbacks 30 | ); 31 | return; 32 | } 33 | 34 | // TODO: Actually implement these callbacks 35 | switch ((ipc_evt_enum_t)task->function_id) { 36 | case ACE_BT_CALLBACK_GATTC_SERVICE_REGISTERED: { 37 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_SERVICE_REGISTERED"); 38 | log_debug("Not actually implemented at all in acebt, so ignore this log"); 39 | } break; 40 | case ACE_BT_CALLBACK_GATTC_SERVICE_DISCOVERED: { 41 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_SERVICE_DISCOVERED"); 42 | if (p_client_callbacks->on_ble_gattc_service_discovered_cb == NULL) { 43 | log_error("[%s()]: on_ble_gattc_service_discovered_cb not implemented", __func__); 44 | break; 45 | } 46 | 47 | dis_req_t* data = (dis_req_t*)task->buffer; 48 | p_client_callbacks->on_ble_gattc_service_discovered_cb( 49 | (bleConnHandle)data->conn_handle, data->out_status 50 | ); 51 | } break; 52 | case ACE_BT_CALLBACK_GATTC_CHARS_READ_RSP: { 53 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_CHARS_READ_RSP"); 54 | if (p_client_callbacks->on_ble_gattc_read_characteristics_cb == NULL) { 55 | log_error("[%s()]: on_ble_gattc_read_characteristics_cb not implemented", __func__); 56 | break; 57 | } 58 | 59 | gattc_read_chars_data_t* data = (gattc_read_chars_data_t*)task->buffer; 60 | 61 | // TODO: Decompiled code does way more assignments. So there is a chance I'm not filling 62 | // the whole struct 63 | data->value.blobValue.data = data->data; 64 | uint32_t len32 = data->data_len; 65 | if (len32 > UINT16_MAX) { 66 | log_error( 67 | "[%s() ACE_BT_CALLBACK_GATTC_CHARS_READ_RSP]: data_len too big: %d", __func__, 68 | data->data_len 69 | ); 70 | len32 = UINT16_MAX; 71 | } 72 | data->value.blobValue.size = (uint16_t)len32; 73 | data->value.blobValue.offset = 0; 74 | data->value.format = BLE_FORMAT_BLOB; 75 | 76 | p_client_callbacks->on_ble_gattc_read_characteristics_cb( 77 | (bleConnHandle)data->conn_handle, data->value, data->status 78 | ); 79 | } break; 80 | case ACE_BT_CALLBACK_GATTC_CHARS_WRITE_RSP: { 81 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_CHARS_WRITE_RSP"); 82 | if (p_client_callbacks->on_ble_gattc_write_characteristics_cb == NULL) { 83 | log_error("[%s()]: on_ble_gattc_write_characteristics_cb not implemented", __func__); 84 | break; 85 | } 86 | 87 | gattc_write_chars_data_t* data = (gattc_write_chars_data_t*)task->buffer; 88 | 89 | // TODO: Decompiled code does way more assignments. This might not be feature complete 90 | data->value.blobValue.data = data->data; 91 | uint32_t len32 = data->data_len; 92 | if (len32 > UINT16_MAX) { 93 | log_error( 94 | "[%s() ACE_BT_CALLBACK_GATTC_CHARS_WRITE_RSP]: data_len too big: %d", __func__, 95 | data->data_len 96 | ); 97 | len32 = UINT16_MAX; 98 | } 99 | data->value.blobValue.size = (uint16_t)len32; 100 | data->value.blobValue.offset = 0; 101 | data->value.format = BLE_FORMAT_BLOB; 102 | 103 | p_client_callbacks->on_ble_gattc_write_characteristics_cb( 104 | (bleConnHandle)data->conn_handle, data->value, data->status 105 | ); 106 | } break; 107 | case ACE_BT_CALLBACK_GATTC_EXEC_WRITE_RSP: { 108 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_EXEC_WRITE_RSP"); 109 | } break; 110 | case ACE_BT_CALLBACK_GATTC_NOTIFY_CHARS_CHANGED: { 111 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_NOTIFY_CHARS_CHANGED"); 112 | if (p_client_callbacks->notify_characteristics_cb == NULL) { 113 | log_error("[%s()]: notify_characteristics_cb not implemented", __func__); 114 | break; 115 | } 116 | 117 | notify_data_t* data = (notify_data_t*)task->buffer; 118 | 119 | // TODO: Decompiled code does way more assignments. This might not be feature complete 120 | data->value.blobValue.data = data->data; 121 | uint32_t len32 = data->data_len; 122 | if (len32 > UINT16_MAX) { 123 | log_error( 124 | "[%s() ACE_BT_CALLBACK_GATTC_NOTIFY_CHARS_CHANGED]: data_len too big: %d", __func__, 125 | data->data_len 126 | ); 127 | len32 = UINT16_MAX; 128 | } 129 | data->value.blobValue.size = (uint16_t)len32; 130 | data->value.blobValue.offset = 0; 131 | data->value.format = BLE_FORMAT_BLOB; 132 | 133 | p_client_callbacks->notify_characteristics_cb( 134 | (bleConnHandle)data->conn_handle, data->value 135 | ); 136 | } break; 137 | case ACE_BT_CALLBACK_GATTC_DESC_WRITE_RSP: { 138 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_DESC_WRITE_RSP"); 139 | if (p_client_callbacks->on_ble_gattc_write_descriptor_cb == NULL) { 140 | log_error("[%s()]: on_ble_gattc_write_descriptor_cb not implemented", __func__); 141 | break; 142 | } 143 | 144 | gattc_write_desc_data_t* data = (gattc_write_desc_data_t*)task->buffer; 145 | 146 | // TODO: Decompiled code does way more assignments. This might not be feature complete 147 | data->desc.blobValue.data = data->data; 148 | uint32_t len32 = data->data_len; 149 | if (len32 > UINT16_MAX) { 150 | log_error( 151 | "[%s() ACE_BT_CALLBACK_GATTC_DESC_WRITE_RSP]: data_len too big: %d", __func__, 152 | data->data_len 153 | ); 154 | len32 = UINT16_MAX; 155 | } 156 | data->desc.blobValue.size = (uint16_t)len32; 157 | data->desc.blobValue.offset = 0; 158 | 159 | // Callback expects a bleGattCharacteristicsValue_t? I think it might be expected that 160 | // only gattDescriptor is populated 161 | bleGattCharacteristicsValue_t charac; 162 | charac.gattDescriptor = data->desc; 163 | 164 | p_client_callbacks->on_ble_gattc_write_descriptor_cb( 165 | (bleConnHandle)data->conn_handle, charac, data->status 166 | ); 167 | } break; 168 | case ACE_BT_CALLBACK_GATTC_DESC_READ_RSP: { 169 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_DESC_READ_RSP"); 170 | } break; 171 | case ACE_BT_CALLBACK_GATTC_GET_DB_RSP: { 172 | log_debug("BLE GATTC callback handler, case ACE_BT_CALLBACK_GATTC_GET_DB_RSP"); 173 | if (p_client_callbacks->on_ble_gattc_get_gatt_db_cb == NULL) { 174 | log_error("[%s()]: on_ble_gattc_get_gatt_db_callback not implemented", __func__); 175 | break; 176 | } 177 | 178 | bleGattsService_t* gatt_service; 179 | uint32_t no_svc; 180 | register_cback_gatts_list_t* data = (register_cback_gatts_list_t*)task->buffer; 181 | 182 | deserealize_all_gatts_register_data(data, &gatt_service, &no_svc); 183 | p_client_callbacks->on_ble_gattc_get_gatt_db_cb( 184 | (bleConnHandle)data->conn_handle, gatt_service, no_svc 185 | ); 186 | cleanup_all_service(gatt_service, no_svc); 187 | } break; 188 | default: 189 | log_error("[%s()]: Unknown GATT Client callback type %d", __func__, task->function_id); 190 | break; 191 | } 192 | 193 | return; 194 | } 195 | -------------------------------------------------------------------------------- /src/kindlebt_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #define ADDR_WITH_COLON_LEN 17 15 | #define ADDR_WITHOUT_COLON_LEN 12 16 | #define PRINT_UUID_STR_LEN 49 17 | 18 | static void remove_all_chars(char* str, char c) { 19 | char *pr = str, *pw = str; 20 | while (*pr) { 21 | *pw = *pr++; 22 | pw += (*pw != c); 23 | } 24 | *pw = '\0'; 25 | } 26 | 27 | void utilsConvertBdAddrToStr(bdAddr_t* paddr, char* outStr) { 28 | sprintf( 29 | outStr, "%02X:%02X:%02X:%02X:%02X:%02X", paddr->address[0], paddr->address[1], 30 | paddr->address[2], paddr->address[3], paddr->address[4], paddr->address[5] 31 | ); 32 | } 33 | 34 | uint8_t utilsConvertCharToHex(char input) { 35 | if (input >= '0' && input <= '9') { 36 | return (uint8_t)(input - '0'); 37 | } else if (input >= 'A' && input <= 'F') { 38 | return (uint8_t)(input - 'A') + 10; 39 | } else if (input >= 'a' && input <= 'f') { 40 | return (uint8_t)(input - 'a') + 10; 41 | } else { 42 | return 0; 43 | } 44 | } 45 | 46 | uint16_t utilsConvertHexStrToByteArray(char* input, uint8_t* output) { 47 | uint8_t length = 0; 48 | char* hex_string = input; 49 | uint8_t hex_length = strlen(input); 50 | 51 | for (int i = 0; i < hex_length; i += 2) { 52 | uint8_t value = utilsConvertCharToHex(hex_string[i]) << 4; 53 | 54 | if (i + 1 < hex_length) { 55 | value |= utilsConvertCharToHex(hex_string[i + 1]); 56 | } 57 | output[length] = value; 58 | length++; 59 | } 60 | 61 | return length; 62 | } 63 | 64 | status_t utilsConvertStrToBdAddr(char* str, bdAddr_t* pAddr) { 65 | if (str == NULL || pAddr == NULL) { 66 | return ACE_STATUS_BAD_PARAM; 67 | } 68 | 69 | int length = strlen(str); 70 | if (length != ADDR_WITH_COLON_LEN && length != ADDR_WITHOUT_COLON_LEN) { 71 | log_error("Invalid string format. Must be xx:xx:xx:xx:xx:xx or xxxxxxxxxxxx"); 72 | return ACE_STATUS_BAD_PARAM; 73 | } 74 | // Check if string is in : format 75 | if (length == ADDR_WITH_COLON_LEN && str[2] == ':' && str[5] == ':' && str[8] == ':' && 76 | str[11] == ':' && str[14] == ':') { 77 | remove_all_chars(str, ':'); 78 | } 79 | 80 | if (strlen(str) != ADDR_WITHOUT_COLON_LEN) { 81 | log_error("Invalid string format. Must be xx:xx:xx:xx:xx:xx or xxxxxxxxxxxx"); 82 | return ACE_STATUS_BAD_PARAM; 83 | } 84 | 85 | for (int i = 0; i < ADDR_WITHOUT_COLON_LEN; i++) { 86 | if (!((str[i] >= '0' && str[i] <= '9') || (str[i] >= 'A' && str[i] <= 'F') || 87 | (str[i] >= 'a' && str[i] <= 'f'))) { 88 | log_error("Contains non-hex character at index %d", i); 89 | return ACE_STATUS_BAD_PARAM; 90 | } 91 | } 92 | 93 | log_debug("Converting str->BT addr, str: %s", str); 94 | 95 | length = utilsConvertHexStrToByteArray(str, pAddr->address); 96 | if (length != MAC_ADDR_LEN) { 97 | return ACE_STATUS_BAD_PARAM; 98 | } 99 | 100 | return ACE_STATUS_OK; 101 | } 102 | 103 | void utilsPrintUuid(char* uuid_str, uuid_t* uuid, int max) { 104 | snprintf( 105 | uuid_str, max, 106 | "%02x %02x %02x %02x %02x %02x %02x %02x %02x" 107 | " %02x %02x %02x %02x %02x %02x %02x", 108 | uuid->uu[0], uuid->uu[1], uuid->uu[2], uuid->uu[3], uuid->uu[4], uuid->uu[5], uuid->uu[6], 109 | uuid->uu[7], uuid->uu[8], uuid->uu[9], uuid->uu[10], uuid->uu[11], uuid->uu[12], 110 | uuid->uu[13], uuid->uu[14], uuid->uu[15] 111 | ); 112 | } 113 | 114 | char* utilsDumpServer(bleGattsService_t* server, char* log_buff, size_t* size, size_t* offset) { 115 | if (!server) return NULL; 116 | 117 | int inc_svc_count = 0; 118 | char buff[PRINT_UUID_STR_LEN]; 119 | memset(buff, 0, sizeof(char) * PRINT_UUID_STR_LEN); 120 | utilsPrintUuid(buff, &server->uuid, PRINT_UUID_STR_LEN); 121 | log_buff = append_to_buffer( 122 | log_buff, size, offset, "Service 0 uuid %s serviceType %d\n", buff, server->serviceType 123 | ); 124 | 125 | struct aceBT_gattIncSvcRec_t* svc_rec; 126 | STAILQ_FOREACH(svc_rec, &server->incSvcList, link) { 127 | memset(buff, 0, sizeof(char) * PRINT_UUID_STR_LEN); 128 | utilsPrintUuid(buff, &svc_rec->value.uuid, PRINT_UUID_STR_LEN); 129 | log_buff = append_to_buffer( 130 | log_buff, size, offset, "Included Services %d service Type %d uuid %s\n", 131 | inc_svc_count++, svc_rec->value.serviceType, buff 132 | ); 133 | } 134 | uint8_t char_count = 0; 135 | struct aceBT_gattCharRec_t* char_rec = NULL; 136 | STAILQ_FOREACH(char_rec, &server->charsList, link) { 137 | memset(buff, 0, sizeof(char) * PRINT_UUID_STR_LEN); 138 | utilsPrintUuid(buff, &char_rec->value.gattRecord.uuid, PRINT_UUID_STR_LEN); 139 | if (char_rec->value.gattDescriptor.is_notify && char_rec->value.gattDescriptor.is_set) { 140 | log_buff = append_to_buffer( 141 | log_buff, size, offset, "\tGatt Characteristics with Notifications %d uuid %s\n", 142 | char_count++, buff 143 | ); 144 | } else { 145 | log_buff = append_to_buffer( 146 | log_buff, size, offset, "\tGatt Characteristics %d uuid %s\n", char_count++, buff 147 | ); 148 | } 149 | 150 | if (char_rec->value.gattDescriptor.is_set) { 151 | utilsPrintUuid( 152 | buff, &char_rec->value.gattDescriptor.gattRecord.uuid, PRINT_UUID_STR_LEN 153 | ); 154 | log_buff = append_to_buffer(log_buff, size, offset, "\t\tDescriptor UUID %s\n", buff); 155 | 156 | } else if (char_rec->value.multiDescCount) { 157 | uint8_t desc_num = 1; 158 | struct aceBT_gattDescRec_t* desc_rec = NULL; 159 | /* Traverse descriptor linked list */ 160 | STAILQ_FOREACH(desc_rec, &char_rec->value.descList, link) { 161 | utilsPrintUuid(buff, &desc_rec->value.gattRecord.uuid, PRINT_UUID_STR_LEN); 162 | log_buff = append_to_buffer( 163 | log_buff, size, offset, "\t\tDescriptor %d UUID %s\n", desc_num++, buff 164 | ); 165 | } 166 | } 167 | } 168 | 169 | return log_buff; 170 | } 171 | 172 | struct aceBT_gattCharRec_t* utilsFindCharRec(uuid_t uuid, uint8_t uuid_len) { 173 | struct aceBT_gattCharRec_t* char_rec = NULL; 174 | 175 | if (!pGgatt_service) { 176 | log_error("GATT DB has not been populated yet!"); 177 | return (NULL); 178 | } 179 | 180 | // Iterate through all services 181 | for (uint32_t i = 0; i < gNo_svc; i++) { 182 | bleGattsService_t* services = &pGgatt_service[i]; 183 | 184 | // Iterate through all characteristics and look for char uuid 185 | STAILQ_FOREACH(char_rec, &services->charsList, link) { 186 | // If char uuid matches, read characteristic 187 | if (!memcmp(char_rec->value.gattRecord.uuid.uu, &uuid.uu, uuid_len)) { 188 | return (char_rec); 189 | } 190 | } 191 | } 192 | log_error("GATT Characteristic UUID could not be found!"); 193 | return (NULL); 194 | } 195 | 196 | void setGattBlobFromBytes( 197 | bleGattCharacteristicsValue_t* chars_value, const uint8_t* data, uint16_t size 198 | ) { 199 | if (chars_value == NULL || data == NULL || size == 0) return; 200 | 201 | free(chars_value->blobValue.data); 202 | 203 | uint8_t* blob = malloc(size); 204 | if (blob == NULL) return; 205 | 206 | memcpy(blob, data, size); 207 | 208 | chars_value->blobValue.data = blob; 209 | chars_value->blobValue.size = size; 210 | chars_value->blobValue.offset = 0; 211 | chars_value->format = BLE_FORMAT_BLOB; 212 | } 213 | 214 | void freeGattBlob(bleGattCharacteristicsValue_t* chars_value) { 215 | if (chars_value == NULL || chars_value->blobValue.data == NULL) return; 216 | 217 | free(chars_value->blobValue.data); 218 | chars_value->blobValue.data = NULL; 219 | chars_value->blobValue.size = 0; 220 | chars_value->blobValue.offset = 0; 221 | } 222 | 223 | status_t waitForCondition(pthread_mutex_t* lock, pthread_cond_t* cond, bool* flag) { 224 | struct timespec ts; 225 | 226 | clock_gettime(CLOCK_REALTIME, &ts); 227 | ts.tv_sec += 10; 228 | 229 | pthread_mutex_lock(lock); 230 | while (!(*flag)) { 231 | int res = pthread_cond_timedwait(cond, lock, &ts); 232 | if (res == ETIMEDOUT) { 233 | pthread_mutex_unlock(lock); 234 | log_error("Timed out waiting for condition"); 235 | return ACE_STATUS_TIMEOUT; 236 | } 237 | } 238 | pthread_mutex_unlock(lock); 239 | 240 | return ACE_STATUS_OK; 241 | } 242 | 243 | void setCallbackVariable(pthread_mutex_t* lock, pthread_cond_t* cond, bool* flag, bool value) { 244 | pthread_mutex_lock(lock); 245 | *flag = value; 246 | pthread_cond_signal(cond); 247 | pthread_mutex_unlock(lock); 248 | } 249 | -------------------------------------------------------------------------------- /src/kindlebt_callbacks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "log.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "kindlebt_application.c" 13 | #include "kindlebt_utils.c" 14 | 15 | pthread_mutex_t callback_vars_lock = PTHREAD_MUTEX_INITIALIZER; 16 | pthread_cond_t callback_vars_cond = PTHREAD_COND_INITIALIZER; 17 | bleCallbackVars_t callback_vars; 18 | bleConnHandle ble_conn_handle; 19 | // GATT get DB global variables 20 | uint32_t gNo_svc; 21 | bleGattsService_t* pGgatt_service = NULL; 22 | 23 | void adapterStateCallback(state_t state) { 24 | if (state == ACEBT_STATE_ENABLED) { 25 | log_debug("Callback %s(): state: STATE_ENABLED", __func__); 26 | setCallbackVariable( 27 | &callback_vars_lock, &callback_vars_cond, &callback_vars.bt_enabled, true 28 | ); 29 | } else if (state == ACEBT_STATE_DISABLED) { 30 | log_debug("Callback %s(): state: STATE_DISABLED", __func__); 31 | setCallbackVariable( 32 | &callback_vars_lock, &callback_vars_cond, &callback_vars.bt_enabled, false 33 | ); 34 | } 35 | } 36 | 37 | void bondStateCallback(status_t status, bdAddr_t* p_remote_addr, aceBT_bondState_t state) { 38 | char addr[MAC_ADDR_STR_LEN]; 39 | memset(addr, 0, MAC_ADDR_STR_LEN); 40 | utilsConvertBdAddrToStr(p_remote_addr, addr); 41 | 42 | switch (state) { 43 | case ACEBT_BOND_STATE_NONE: 44 | log_debug("Callback %s(): status: %d addr: %s state: NONE", __func__, status, addr); 45 | break; 46 | case ACEBT_BOND_STATE_BONDING: 47 | log_debug("Callback %s(): status: %d addr: %s state: BONDING", __func__, status, addr); 48 | break; 49 | case ACEBT_BOND_STATE_BONDED: 50 | log_debug("Callback %s(): status: %d addr: %s state: BONDED", __func__, status, addr); 51 | break; 52 | default: 53 | log_debug( 54 | "Callback %s(): status: %d addr: %s state: UNKNOWN(%d)", __func__, status, addr, state 55 | ); 56 | break; 57 | } 58 | } 59 | 60 | void bleMtuUpdatedCallback(status_t status, bleConnHandle conn_handle, int mtu) { 61 | log_debug( 62 | "Callback %s(): status %d, mtu %d, conn_handle %p", __func__, status, mtu, conn_handle 63 | ); 64 | 65 | if (status == ACE_STATUS_OK) 66 | setCallbackVariable(&callback_vars_lock, &callback_vars_cond, &callback_vars.mtu_set, true); 67 | } 68 | 69 | void bleRegCallback(status_t status) { 70 | log_debug("Callback %s(): status: %d\n", __func__, status); 71 | 72 | if (status == ACE_STATUS_OK) 73 | setCallbackVariable( 74 | &callback_vars_lock, &callback_vars_cond, &callback_vars.ble_registered, true 75 | ); 76 | } 77 | 78 | void bleConnStateChangedCallback( 79 | bleConnState_t state, gattStatus_t status, const bleConnHandle conn_handle, bdAddr_t* p_addr 80 | ) { 81 | char addr[MAC_ADDR_STR_LEN]; 82 | memset(addr, 0, MAC_ADDR_STR_LEN); 83 | utilsConvertBdAddrToStr(p_addr, addr); 84 | log_debug( 85 | "Callback %s(): state %d, status %d, conn_handle %p addr %s", __func__, state, status, 86 | conn_handle, addr 87 | ); 88 | 89 | ble_conn_handle = conn_handle; 90 | 91 | if (status == ACEBT_GATT_STATUS_SUCCESS) { 92 | if (state == ACEBT_BLE_STATE_CONNECTED) { 93 | log_info("BLE device %s connected", addr); 94 | setCallbackVariable( 95 | &callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_connected, true 96 | ); 97 | setCallbackVariable( 98 | &callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_disconnected, false 99 | ); 100 | } else if (state == ACEBT_BLE_STATE_DISCONNECTED) { 101 | log_info("BLE device %s disconnected", addr); 102 | setCallbackVariable( 103 | &callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_connected, false 104 | ); 105 | setCallbackVariable( 106 | &callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_disconnected, true 107 | ); 108 | } 109 | } 110 | } 111 | 112 | void bleGattcServiceDiscoveredCallback(bleConnHandle conn_handle, status_t status) { 113 | log_debug("Callback %s(): conn_handle %p status %d", __func__, conn_handle, status); 114 | 115 | if (status == ACE_STATUS_OK) { 116 | setCallbackVariable( 117 | &callback_vars_lock, &callback_vars_cond, &callback_vars.gattc_discovered, true 118 | ); 119 | } 120 | } 121 | 122 | void bleGattcGetDbCallback( 123 | bleConnHandle conn_handle, bleGattsService_t* gatt_service, uint32_t no_svc 124 | ) { 125 | log_debug("Callback %s(): conn_handle %p no_svc %" PRIu32 "", __func__, conn_handle, no_svc); 126 | 127 | gNo_svc = no_svc; 128 | status_t status = bleCloneGattService(&pGgatt_service, gatt_service, gNo_svc); 129 | 130 | if (status != ACE_STATUS_OK) { 131 | log_error("Error copying GATT Database %d\n", status); 132 | return; 133 | } 134 | 135 | size_t size = 1024, offset = 0; 136 | char* log_buff = malloc(size); 137 | if (!log_buff) return; 138 | 139 | for (uint32_t i = 0; i < no_svc; i++) { 140 | log_buff = append_to_buffer( 141 | log_buff, &size, &offset, "GATT Database index :%" PRIu32 " %p\n", i, &pGgatt_service[i] 142 | ); 143 | log_buff = utilsDumpServer(&pGgatt_service[i], log_buff, &size, &offset); 144 | } 145 | 146 | log_debug("%s", log_buff); 147 | free(log_buff); 148 | 149 | setCallbackVariable(&callback_vars_lock, &callback_vars_cond, &callback_vars.got_gatt_db, true); 150 | } 151 | 152 | void bleGattcNotifyCharsCallback( 153 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value 154 | ) { 155 | log_debug("Callback %s(): conn_handle %p", __func__, conn_handle); 156 | 157 | char buff[256]; 158 | utilsPrintUuid(buff, &chars_value.gattRecord.uuid, 256); 159 | log_debug("%s() UUID:: %s", __func__, buff); 160 | 161 | size_t size = 1024, offset = 0; 162 | char* log_buff = malloc(size); 163 | if (!log_buff) return; 164 | 165 | log_buff = append_to_buffer(log_buff, &size, &offset, "%s() DATA:: ", __func__); 166 | for (int idx = 0; idx < chars_value.blobValue.size; idx++) 167 | log_buff = 168 | append_to_buffer(log_buff, &size, &offset, "%x", chars_value.blobValue.data[idx]); 169 | 170 | log_debug("%s", log_buff); 171 | free(log_buff); 172 | } 173 | 174 | void bleGattcReadCharsCallback( 175 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value, status_t status 176 | ) { 177 | log_debug("Callback %s(): status %d conn_handle %p", __func__, status, conn_handle); 178 | 179 | char buff[256]; 180 | utilsPrintUuid(buff, &chars_value.gattRecord.uuid, 256); 181 | log_debug("%s() UUID:: %s", __func__, buff); 182 | 183 | size_t size = 1024, offset = 0; 184 | char* log_buff = malloc(size); 185 | if (!log_buff) return; 186 | 187 | log_buff = append_to_buffer(log_buff, &size, &offset, "%s() DATA:: ", __func__); 188 | for (int idx = 0; idx < chars_value.blobValue.size; idx++) 189 | log_buff = 190 | append_to_buffer(log_buff, &size, &offset, "%x", chars_value.blobValue.data[idx]); 191 | 192 | log_debug("%s", log_buff); 193 | free(log_buff); 194 | } 195 | 196 | void bleGattcWriteCharsCallback( 197 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t gatt_characteristics, status_t status 198 | ) { 199 | log_debug( 200 | "Callback %s(): conn_handle %p GATT format %u", __func__, conn_handle, 201 | gatt_characteristics.format 202 | ); 203 | } 204 | 205 | // Wrappers needed for when we need to share callbacks between library and application 206 | void bleGattcServiceDiscoveredCallbackWrapper(bleConnHandle conn_handle, status_t status) { 207 | bleGattcServiceDiscoveredCallback(conn_handle, status); 208 | 209 | if (application_gatt_client_callbacks.on_ble_gattc_service_discovered_cb != NULL) { 210 | application_gatt_client_callbacks.on_ble_gattc_service_discovered_cb(conn_handle, status); 211 | } 212 | } 213 | 214 | void bleGattcGetDbCallbackWrapper( 215 | bleConnHandle conn_handle, bleGattsService_t* gatt_service, uint32_t no_svc 216 | ) { 217 | bleGattcGetDbCallback(conn_handle, gatt_service, no_svc); 218 | 219 | if (application_gatt_client_callbacks.on_ble_gattc_get_gatt_db_cb != NULL) { 220 | application_gatt_client_callbacks.on_ble_gattc_get_gatt_db_cb( 221 | conn_handle, gatt_service, no_svc 222 | ); 223 | } 224 | } 225 | 226 | void bleGattcNotifyCharsCallbackWrapper( 227 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value 228 | ) { 229 | // bleGattcNotifyCharsCallback(conn_handle, chars_value); 230 | 231 | if (application_gatt_client_callbacks.notify_characteristics_cb != NULL) { 232 | application_gatt_client_callbacks.notify_characteristics_cb(conn_handle, chars_value); 233 | } 234 | } 235 | 236 | void bleGattcReadCharsCallbackWrapper( 237 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value, status_t status 238 | ) { 239 | // bleGattcReadCharsCallback(conn_handle, chars_value, status); 240 | 241 | if (application_gatt_client_callbacks.on_ble_gattc_read_characteristics_cb != NULL) { 242 | application_gatt_client_callbacks.on_ble_gattc_read_characteristics_cb( 243 | conn_handle, chars_value, status 244 | ); 245 | } 246 | } 247 | 248 | void bleGattcWriteCharsCallbackWrapper( 249 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t gatt_characteristics, status_t status 250 | ) { 251 | // bleGattcWriteCharsCallback(conn_handle, gatt_characteristics, status); 252 | 253 | if (application_gatt_client_callbacks.on_ble_gattc_write_characteristics_cb != NULL) { 254 | application_gatt_client_callbacks.on_ble_gattc_write_characteristics_cb( 255 | conn_handle, gatt_characteristics, status 256 | ); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /examples/basic_usage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | static sessionHandle bt_session = NULL; 8 | static bleConnHandle conn_handle = NULL; 9 | static bleGattsService_t gatt_db; 10 | 11 | // For our dumb callback waits implementation 12 | static bool read_characteristic = false; 13 | static bool wrote_characteristic = false; 14 | 15 | // ===== UTILS ===== 16 | void print_uuid(uuid_t* uuid) { 17 | printf( 18 | "UUID: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", 19 | uuid->uu[0], uuid->uu[1], uuid->uu[2], uuid->uu[3], uuid->uu[4], uuid->uu[5], uuid->uu[5], 20 | uuid->uu[7], uuid->uu[8], uuid->uu[9], uuid->uu[10], uuid->uu[11], uuid->uu[12], 21 | uuid->uu[13], uuid->uu[14], uuid->uu[15] 22 | ); 23 | } 24 | 25 | // This is a very simple implementation. In some cases, the response might not even be 26 | // a @ref bleGattBlobValue_t like we're assuming here 27 | // Another thing to note is that the \c data field is just a \c uint8_t* field. If you 28 | // expect text, you'll need to cast it with an ASCII table 29 | void print_data(bleGattBlobValue_t* value) { 30 | printf("Data: "); 31 | for (int idx = 0; idx < value->size; idx++) 32 | printf("%02x ", value->data[idx]); 33 | printf("\n"); 34 | } 35 | 36 | void freeGattBlob(bleGattCharacteristicsValue_t* chars_value) { 37 | if (chars_value == NULL || chars_value->blobValue.data == NULL) return; 38 | 39 | free(chars_value->blobValue.data); 40 | chars_value->blobValue.data = NULL; 41 | chars_value->blobValue.size = 0; 42 | chars_value->blobValue.offset = 0; 43 | } 44 | 45 | void setGattBlobFromBytes( 46 | bleGattCharacteristicsValue_t* chars_value, const uint8_t* data, uint16_t size 47 | ) { 48 | if (chars_value == NULL || data == NULL || size == 0) return; 49 | 50 | free(chars_value->blobValue.data); 51 | 52 | uint8_t* blob = malloc(size); 53 | if (blob == NULL) return; 54 | 55 | memcpy(blob, data, size); 56 | 57 | chars_value->blobValue.data = blob; 58 | chars_value->blobValue.size = size; 59 | chars_value->blobValue.offset = 0; 60 | chars_value->format = BLE_FORMAT_BLOB; 61 | } 62 | 63 | // ===== CALLBACKS ===== 64 | void bleGattcNotifyCharsCallback( 65 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value 66 | ) { 67 | printf("Callback %s(): conn_handle %p", __func__, conn_handle); 68 | print_uuid(&chars_value.gattRecord.uuid); 69 | print_data(&chars_value.blobValue); 70 | } 71 | 72 | void bleGattcReadCharsCallback( 73 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t chars_value, status_t status 74 | ) { 75 | printf("Callback %s(): status %d conn_handle %p\n", __func__, status, conn_handle); 76 | print_uuid(&chars_value.gattRecord.uuid); 77 | print_data(&chars_value.blobValue); 78 | read_characteristic = true; 79 | } 80 | 81 | void bleGattcWriteCharsCallback( 82 | bleConnHandle conn_handle, bleGattCharacteristicsValue_t gatt_characteristics, status_t status 83 | ) { 84 | printf("Callback %s(): conn_handle %p status %d", __func__, conn_handle, status); 85 | wrote_characteristic = true; 86 | } 87 | 88 | static bleGattClientCallbacks_t gatt_app_callbacks = { 89 | .size = sizeof(bleGattClientCallbacks_t), 90 | .notify_characteristics_cb = bleGattcNotifyCharsCallback, 91 | .on_ble_gattc_read_characteristics_cb = bleGattcReadCharsCallback, 92 | .on_ble_gattc_write_characteristics_cb = bleGattcWriteCharsCallback, 93 | }; 94 | 95 | int main() { 96 | // (OPTIONAL) SET LOG LEVEL 97 | kindlebt_set_log_level(LOG_LEVEL_DEBUG); 98 | 99 | // USE BLUETOOTH PRIVILEGES 100 | // The ace_bt stuff won't run under root user 101 | if (setgid((gid_t)1003) || setuid((uid_t)1003)) { 102 | fprintf(stderr, "Can't drop privileges to bluetooth user/group\n"); 103 | return -1; 104 | } 105 | 106 | printf("Hello World from Kindle!\n"); 107 | 108 | // CHECK FOR BLE SUPPORT 109 | // Might not be necessary 110 | bool isBLE = isBLESupported(); 111 | printf("Is BLE enabled: %d\n", isBLE); 112 | if (!isBLE) { 113 | fprintf(stderr, "BLE is not enabled\n"); 114 | return -2; 115 | } 116 | 117 | status_t status; 118 | 119 | // OPEN SESSION 120 | // Necessary, basically all API operations require a BT session 121 | status = openSession(ACEBT_SESSION_TYPE_DUAL_MODE, &bt_session); 122 | printf( 123 | "Opened session status %d, session %p (u32 %u)\n", status, bt_session, (uint32_t)bt_session 124 | ); 125 | if (status != ACE_STATUS_OK) { 126 | fprintf(stderr, "Cannot open Bluetooth session, status: %d\n", status); 127 | return -3; 128 | } 129 | 130 | // REGISTER FOR BLE USE 131 | // This registers for BLE and related callbacks functionality in btmanagerd 132 | status = bleRegister(bt_session); 133 | printf("Registered BLE: %d\n", status); 134 | if (status != ACE_STATUS_OK) { 135 | fprintf(stderr, "Cannot register BLE, status: %d\n", status); 136 | return -4; 137 | } 138 | 139 | // REGISTER FOR GATT CLIENT USE 140 | // Likewise, same as before but specifically for GATTC events in btmanagerd 141 | status = bleRegisterGattClient(bt_session, &gatt_app_callbacks); 142 | printf("Registered GATT Client status: %d\n", status); 143 | if (status != ACE_STATUS_OK) { 144 | fprintf(stderr, "Cannot register GATT Client, status: %d\n", status); 145 | return -5; 146 | } 147 | 148 | // CONNECT TO BLE DEVICE 149 | // Untested whether connecting to a Classic device with this API works 150 | bdAddr_t ble_addr = {.address = {0x2C, 0xCF, 0x67, 0xB8, 0xDC, 0x3F}}; 151 | status = bleConnect( 152 | bt_session, &conn_handle, &ble_addr, ACE_BT_BLE_CONN_PARAM_BALANCED, 153 | ACEBT_BLE_GATT_CLIENT_ROLE, ACE_BT_BLE_CONN_PRIO_MEDIUM 154 | ); 155 | printf("Connected to BLE status:%d\n", status); 156 | if (status != ACE_STATUS_OK) { 157 | fprintf(stderr, "Cannot connect to BLE device, status: %d\n", status); 158 | return -6; 159 | } 160 | 161 | // DISCOVER GATT SERVICES 162 | // Necessary step to discover all the GATT services of the remote device 163 | status = bleDiscoverAllServices(bt_session, conn_handle); 164 | printf("Discovered all services: %d\n", status); 165 | if (status != ACE_STATUS_OK) { 166 | fprintf(stderr, "Cannot discover services of BLE device, status: %d\n", status); 167 | return -7; 168 | } 169 | 170 | // RETRIEVE GATT SERVICES (DB) 171 | // Retrieval of services is a different and also necessary step from discovery 172 | // Even if you know the Characteristic UUIDs ahead of time, you still need to 173 | // discover and retrieve the services 174 | status = bleGetDatabase(conn_handle, &gatt_db); 175 | printf("Requested GATT DB status: %d\n", status); 176 | if (status != ACE_STATUS_OK) { 177 | fprintf(stderr, "Cannot retrieve services of BLE device, status: %d\n", status); 178 | return -8; 179 | } 180 | 181 | // FINDING CHARACTERISTIC 182 | // Again, even if you know beforehand the UUID of the characteristic you want, you 183 | // still need to retrieve the DB, and then find your UUID in the local DB copy to 184 | // interact with it 185 | uuid_t characUuid = { 186 | .uu = 187 | {0xFF, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 188 | 0x00, 0x00}, 189 | .type = ACEBT_UUID_TYPE_16, 190 | }; 191 | struct aceBT_gattCharRec_t* charac = utilsFindCharRec(characUuid, 16); 192 | if (charac == NULL) { 193 | printf("Couldn't find the characteristic"); 194 | return -9; 195 | } 196 | 197 | // ENABLING NOTIFICATIONS ON CHARACTERISTIC 198 | status = bleSetNotification(bt_session, conn_handle, charac->value, true); 199 | if (status != ACE_STATUS_OK) { 200 | fprintf( 201 | stderr, "Cannot enable notifications on BLE device/characteristic, status: %d\n", status 202 | ); 203 | return -10; 204 | } 205 | 206 | sleep(5); 207 | 208 | // DISABLING NOTIFICATIONS ON CHARACTERISTIC 209 | status = bleSetNotification(bt_session, conn_handle, charac->value, false); 210 | if (status != ACE_STATUS_OK) { 211 | fprintf( 212 | stderr, "Cannot disable notifications on BLE device/characteristic, status: %d\n", 213 | status 214 | ); 215 | return -10; 216 | } 217 | 218 | sleep(2); 219 | 220 | // Block reading and writing in a loop 221 | for (int i = 0; i < 20; i++) { 222 | // READING CHARACTERISTIC 223 | status = bleReadCharacteristic(bt_session, conn_handle, charac->value); 224 | if (status != ACE_STATUS_OK) { 225 | fprintf(stderr, "Cannot read characteristic, status: %d\n", status); 226 | return -11; 227 | } 228 | for (int j = 0; j < 10; j++) { 229 | if (read_characteristic) break; 230 | sleep(1); 231 | } 232 | if (!read_characteristic) { 233 | fprintf(stderr, "Timed out waiting to read characteristic, status: %d\n", status); 234 | return -11; 235 | } 236 | read_characteristic = false; 237 | // Not sure if necessary but I always reset the blob after read/write 238 | freeGattBlob(&charac->value); 239 | 240 | // WRITING CHARACTERISTIC 241 | // Values used in my characteristic as an example of how to write 242 | uint8_t off[] = {'O', 'F', 'F'}; 243 | uint8_t on[] = {'O', 'N'}; 244 | uint8_t* states[] = {off, on}; 245 | size_t lengths[] = {sizeof(off), sizeof(on)}; 246 | 247 | // Now write the value to the characteristic struct 248 | setGattBlobFromBytes(&charac->value, states[i % 2], lengths[i % 2]); 249 | 250 | status = bleWriteCharacteristic( 251 | bt_session, conn_handle, &charac->value, ACEBT_BLE_WRITE_TYPE_RESP_REQUIRED 252 | ); 253 | if (status != ACE_STATUS_OK) { 254 | fprintf(stderr, "Cannot write characteristic, status: %d\n", status); 255 | return -12; 256 | } 257 | for (int j = 0; j < 10; j++) { 258 | if (wrote_characteristic) break; 259 | sleep(1); 260 | } 261 | if (!wrote_characteristic) { 262 | fprintf(stderr, "Timed out waiting to write characteristic, status: %d\n", status); 263 | return -12; 264 | } 265 | wrote_characteristic = false; 266 | freeGattBlob(&charac->value); 267 | } 268 | 269 | sleep(2); 270 | 271 | status = bleDisconnect(conn_handle); 272 | printf("Disconnected from BLE status: %d\n", status); 273 | 274 | status = bleDeregisterGattClient(bt_session); 275 | printf("Deregistered GATT Client status: %d\n", status); 276 | 277 | status = bleDeregister(bt_session); 278 | printf("Deregistered BLE status: %d\n", status); 279 | 280 | status = closeSession(bt_session); 281 | printf("Closed session status: %d\n", status); 282 | 283 | return 0; 284 | } 285 | -------------------------------------------------------------------------------- /manual/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /src/compat_ace_implementations.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "log.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * Static local helper function to create a callback mask based on the 18 | * p_callbacks passed by app client. 19 | * 20 | * @param p_callbacks pointer to callbacks struct bleGattClientCallbacks_t 21 | * @return uint16_t callback supports up to 16 mask 22 | */ 23 | static uint16_t create_client_callback_mask(bleGattClientCallbacks_t* p_callbacks) { 24 | uint16_t cback_mask = 0; 25 | if (p_callbacks == NULL || p_callbacks->size == 0) return cback_mask; 26 | cback_mask |= p_callbacks->on_ble_gattc_service_registered_cb 27 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_REGISTERED 28 | : 0; 29 | cback_mask |= p_callbacks->on_ble_gattc_service_discovered_cb 30 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DISCOVERED 31 | : 0; 32 | cback_mask |= p_callbacks->on_ble_gattc_read_characteristics_cb 33 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_READ_CHARS 34 | : 0; 35 | cback_mask |= p_callbacks->on_ble_gattc_write_characteristics_cb 36 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_WRITE_CHARS 37 | : 0; 38 | cback_mask |= p_callbacks->notify_characteristics_cb 39 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_NOTIFY_CHARS 40 | : 0; 41 | cback_mask |= p_callbacks->on_ble_gattc_write_descriptor_cb 42 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DESC_WRITE 43 | : 0; 44 | cback_mask |= p_callbacks->on_ble_gattc_read_descriptor_cb 45 | ? ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DESC_READ 46 | : 0; 47 | cback_mask |= p_callbacks->on_ble_gattc_get_gatt_db_cb ? ACEBT_IPC_CBACK_MASK_GATTC_GET_DB : 0; 48 | cback_mask |= 49 | p_callbacks->on_ble_gattc_execute_write_cb ? ACEBT_IPC_CBACK_MASK_GATTC_EXECUTE_WRITE : 0; 50 | return cback_mask; 51 | } 52 | 53 | status_t pre5170_bleRegisterGattClient( 54 | sessionHandle session_handle, bleGattClientCallbacks_t* callbacks, bleAppId_t app_id 55 | ) { 56 | status_t status; 57 | uint16_t mask = 0; 58 | aipcHandles_t aipc_handle; 59 | registerCbackGattcData_t manager_callbacks; 60 | 61 | status = getSessionInfo(session_handle, &aipc_handle); 62 | if (status != ACE_STATUS_OK) { 63 | log_error("[%s()]: Couldn't get session info. Result %d", __func__, status); 64 | return ACE_STATUS_BAD_PARAM; 65 | } 66 | 67 | dump_aipc_handle(aipc_handle); 68 | 69 | mask = create_client_callback_mask(callbacks); 70 | dump_mask_bits(mask); 71 | 72 | // In theory this should match session_handle exactly 73 | uint32_t temp_aipc = (aipc_handle.server_id << 16) + aipc_handle.callback_server_id; 74 | log_debug("[%s()]: temp_aipc %u (0x%04x)", __func__, temp_aipc, temp_aipc); 75 | aceBt_serializeGattcRegisterData(&manager_callbacks, temp_aipc, mask, app_id); 76 | log_debug("[%s()]: Register GATT Client session handle %p", __func__, session_handle); 77 | 78 | dump_registerCbackGattcData(&manager_callbacks); 79 | 80 | status = registerBTClientData(session_handle, CALLBACK_INDEX_BLE_GATT_CLIENT, (void*)callbacks); 81 | log_debug("[%s()]: registerBTClientData step. Result: %d", __func__, status); 82 | if (status != ACEBT_STATUS_SUCCESS) return status; 83 | 84 | status = registerBTEvtHandler( 85 | session_handle, pre5170_gattc_cb_handler, ACE_BT_CALLBACK_GATTC_INIT, 86 | ACE_BT_CALLBACK_GATTC_MAX 87 | ); 88 | log_debug("[%s()]: registerBTEvtHandler step. Result: %d", __func__, status); 89 | if (status != ACEBT_STATUS_SUCCESS) return status; 90 | 91 | status = aipc_invoke_sync_call( 92 | ACE_BT_BLE_REGISTER_GATT_CLIENT_API, (void*)&manager_callbacks, manager_callbacks.size 93 | ); 94 | if (status != ACE_STATUS_OK) { 95 | log_error( 96 | "[%s()]: failed to register gatt client callbacks with server! result: %d", __func__, 97 | status 98 | ); 99 | registerBTClientData(session_handle, CALLBACK_INDEX_BLE_GATT_CLIENT, NULL); 100 | registerBTEvtHandler( 101 | session_handle, NULL, ACE_BT_CALLBACK_GATTC_INIT, ACE_BT_CALLBACK_GATTC_MAX 102 | ); 103 | } 104 | 105 | return status; 106 | } 107 | 108 | status_t pre5170_bleDeregisterGattClient(sessionHandle session_handle) { 109 | status_t status; 110 | gatt_request_unreg_t unregister; 111 | 112 | status = getSessionInfo(session_handle, &unregister.h1); 113 | if (status != ACE_STATUS_OK) { 114 | log_error("[%s()]: Couldn't get session info. Result %d", __func__, status); 115 | return ACE_STATUS_BAD_PARAM; 116 | } 117 | 118 | dump_aipc_handle(unregister.h1); 119 | 120 | unregister.h2 = unregister.h1; 121 | unregister.size = sizeof(unregister); 122 | 123 | status = 124 | aipc_invoke_sync_call(ACE_BT_BLE_UNREGISTER_GATT_CLIENT_API, &unregister, unregister.size); 125 | if (status != ACE_STATUS_OK) { 126 | log_error("[%s()]: Failed to send AIPC call %d", __func__, status); 127 | } 128 | 129 | return status; 130 | } 131 | 132 | status_t pre5170_bleDiscoverAllServices(sessionHandle session_handle, bleConnHandle conn_handle) { 133 | log_debug("Called into pre 5.17 %s", __func__); 134 | 135 | status_t status; 136 | request_disc_all_svc_t data; 137 | 138 | serialize_gattc_disc_all_svc(&data, (uint32_t)conn_handle); 139 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_DISC_ALL_SVC_API, &data, data.size); 140 | if (status != ACE_STATUS_OK) { 141 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 142 | } 143 | return data.out_status; 144 | } 145 | 146 | status_t pre5170_bleGetService(bleConnHandle conn_handle) { 147 | log_debug("Called into pre 5.17 %s", __func__); 148 | 149 | status_t status; 150 | gattc_get_db_data_t data; 151 | 152 | serialize_ble_get_db_req(&data, (uint32_t)conn_handle, NULL); 153 | dump_gattc_get_db_data_t(&data); 154 | 155 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_GET_SERVICE_API, &data, data.size); 156 | if (status != ACE_STATUS_OK) { 157 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 158 | } 159 | return status; 160 | } 161 | 162 | status_t pre5170_bleReadCharacteristic( 163 | sessionHandle session_handle, bleConnHandle conn_handle, 164 | bleGattCharacteristicsValue_t chars_value 165 | ) { 166 | log_debug("Called into pre 5.17 %s", __func__); 167 | 168 | status_t status; 169 | aipcHandles_t handle; 170 | 171 | status = getSessionInfo(session_handle, &handle); 172 | if (status != ACE_STATUS_OK) { 173 | log_error("[%s()]: Couldn't get session info. Result: %d", __func__, status); 174 | return ACE_STATUS_BAD_PARAM; 175 | } 176 | 177 | acebt_gattc_read_chars_req_data_t data; 178 | serialize_gattc_read_chars_req(&data, (uint32_t)conn_handle, chars_value); 179 | log_debug("[%s()]: Serialize request, status: %d", __func__, data.status); 180 | 181 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_READ_CHARS_API, &data, data.size); 182 | if (status != ACE_STATUS_OK) { 183 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 184 | } 185 | return status; 186 | } 187 | 188 | status_t pre5170_bleWriteCharacteristics( 189 | sessionHandle session_handle, bleConnHandle conn_handle, 190 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 191 | ) { 192 | log_debug("Called into pre 5.17 %s", __func__); 193 | 194 | status_t status; 195 | aipcHandles_t handle; 196 | 197 | status = getSessionInfo(session_handle, &handle); 198 | if (status != ACE_STATUS_OK) { 199 | log_error("[%s()]: Couldn't get session info. Result: %d", __func__, status); 200 | return ACE_STATUS_BAD_PARAM; 201 | } 202 | 203 | uint8_t* out_data; 204 | uint32_t out_len; 205 | 206 | serialize_gattc_write_char_req( 207 | (uint32_t)conn_handle, chars_value, &out_data, &out_len, request_type 208 | ); 209 | 210 | if (out_data == NULL) { 211 | log_error("[%s()]: Failed serialize_gattc_write_char_req", __func__); 212 | return ACE_STATUS_GENERAL_ERROR; 213 | } 214 | 215 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_WRITE_CHARS_API, out_data, out_len); 216 | if (status != ACE_STATUS_OK) { 217 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 218 | return status; 219 | } 220 | 221 | shadow_aceAlloc_free(out_data); 222 | 223 | return status; 224 | } 225 | 226 | status_t pre5170_bleWriteDescriptor( 227 | sessionHandle session_handle, bleConnHandle conn_handle, 228 | bleGattCharacteristicsValue_t* chars_value, responseType_t request_type 229 | ) { 230 | log_debug("Called into pre 5.17 %s", __func__); 231 | 232 | status_t status; 233 | aipcHandles_t handle; 234 | 235 | status = getSessionInfo(session_handle, &handle); 236 | if (status != ACE_STATUS_OK) { 237 | log_error("[%s()]: Couldn't get session info. Result: %d", __func__, status); 238 | return ACE_STATUS_BAD_PARAM; 239 | } 240 | 241 | uint8_t* out_data; 242 | uint32_t out_len; 243 | 244 | serialize_gattc_write_desc_req( 245 | (uint32_t)conn_handle, &chars_value->gattDescriptor, &out_data, &out_len, request_type 246 | ); 247 | if (out_data == NULL) { 248 | log_error("[%s()]: serialize_gattc_write_desc_req() failed", __func__); 249 | return ACE_STATUS_GENERAL_ERROR; 250 | } 251 | 252 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_WRITE_DESC_API, out_data, out_len); 253 | if (status == ACE_STATUS_OK) { 254 | status = ((const gattc_write_desc_data_t*)out_data)->status; 255 | } else { 256 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 257 | status = ACE_STATUS_BAD_PARAM; 258 | } 259 | 260 | shadow_aceAlloc_free(out_data); 261 | 262 | return status; 263 | } 264 | 265 | status_t pre5170_bleSetNotification( 266 | sessionHandle session_handle, bleConnHandle conn_handle, 267 | bleGattCharacteristicsValue_t chars_value, bool enable 268 | ) { 269 | log_debug("Called into pre 5.17 %s", __func__); 270 | 271 | status_t status; 272 | aipcHandles_t handle; 273 | 274 | status = getSessionInfo(session_handle, &handle); 275 | if (status != ACE_STATUS_OK) { 276 | log_error("[%s()]: Couldn't get session info. Result: %d", __func__, status); 277 | return ACE_STATUS_BAD_PARAM; 278 | } 279 | 280 | gattc_set_notify_data_t data; 281 | serialize_ble_set_notify(&data, (uint32_t)conn_handle, chars_value, enable); 282 | 283 | status = aipc_invoke_sync_call(ACE_BT_BLE_GATT_CLIENT_SET_NOTIFY_API, &data, data.size); 284 | if (status != ACE_STATUS_OK) { 285 | log_error("[%s()]: Failed to send AIPC call. Status: %d", __func__, status); 286 | return status; 287 | } 288 | 289 | struct aceBT_gattDescRec_t* desc_rec = NULL; 290 | STAILQ_FOREACH(desc_rec, &chars_value.descList, link) { 291 | if (desc_rec->value.is_set && desc_rec->value.is_notify) break; 292 | } 293 | 294 | if (desc_rec == NULL) { 295 | log_error( 296 | "[%s()]: Couldn't find CCCD descriptor. Are you sure this characteristic supports " 297 | "notifications?", 298 | __func__ 299 | ); 300 | return ACE_STATUS_BAD_PARAM; 301 | } 302 | 303 | // Store our enable/disable option as little endian directly 304 | uint8_t subscription[2]; 305 | uint16_t le = htole16((uint16_t)enable); 306 | memcpy(subscription, &le, sizeof(le)); 307 | 308 | // Set the linked list find as the main gattDescriptor for calls to WriteDescriptor 309 | chars_value.gattDescriptor.gattRecord.handle = desc_rec->value.gattRecord.handle; 310 | chars_value.gattDescriptor.blobValue.data = malloc(sizeof(subscription)); 311 | if (chars_value.gattDescriptor.blobValue.data == NULL) { 312 | log_error("[%s()]: Couldn't allocate memory for CCCD descriptor?", __func__); 313 | return ACE_STATUS_OUT_OF_MEMORY; 314 | } 315 | chars_value.gattDescriptor.blobValue.offset = 0; 316 | chars_value.gattDescriptor.blobValue.size = sizeof(subscription); 317 | memcpy(chars_value.gattDescriptor.blobValue.data, subscription, sizeof(subscription)); 318 | 319 | status = shim_bleWriteDescriptor( 320 | session_handle, conn_handle, &chars_value, ACEBT_BLE_WRITE_TYPE_RESP_REQUIRED 321 | ); 322 | 323 | return status; 324 | } 325 | -------------------------------------------------------------------------------- /include/kindlebt/compat_ace_internals.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_ACE_INTERNALS_H 2 | #define COMPAT_ACE_INTERNALS_H 3 | 4 | #include "kindlebt_defines.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct { 11 | uint16_t callback_server_id; 12 | uint16_t server_id; 13 | } aceBT_aipcHandles_t; 14 | typedef aceBT_aipcHandles_t aipcHandles_t; 15 | 16 | typedef struct { 17 | uint32_t size; 18 | uint32_t session_handle; 19 | status_t out_status; 20 | } __attribute__((packed)) acebt_request_unreg_t; 21 | typedef acebt_request_unreg_t request_unreg_t; 22 | 23 | // Reversed. Don't ask me why the repeated aipcHandles_t 24 | // Used for GATT Client/Server deregister requests 25 | typedef struct { 26 | uint32_t size; 27 | aipcHandles_t h1; 28 | aipcHandles_t h2; 29 | } gatt_request_unreg_t; 30 | 31 | status_t getSessionInfo(sessionHandle sessionHandle, aipcHandles_t* handles); 32 | 33 | typedef struct { 34 | uint16_t size; 35 | uint32_t session_handle; 36 | uint16_t callback_mask; 37 | uint8_t is_unregister; 38 | bleAppId_t app_id; 39 | status_t out_status; 40 | } aceBtMgr_registerCbackGattcData_t; 41 | typedef aceBtMgr_registerCbackGattcData_t registerCbackGattcData_t; 42 | 43 | void aceBt_serializeGattcRegisterData( 44 | registerCbackGattcData_t* data, uint32_t session_handle, uint16_t mask, bleAppId_t app_id 45 | ); 46 | 47 | // Used for dataIndex 48 | #define CALLBACK_INDEX_CLASSIC_DEV_MGR 0x00 49 | #define CALLBACK_INDEX_COMMON_SECURITY 0x01 50 | #define CALLBACK_INDEX_BLE_BEACON_MGR 0x02 51 | #define CALLBACK_INDEX_BLE_GATT_SERVER 0x03 52 | #define CALLBACK_INDEX_BLE_ADAPTER 0x04 53 | #define CALLBACK_INDEX_SOCKET_SERVER 0x05 54 | #define CALLBACK_INDEX_SOCKET_CLIENT 0x06 55 | #define CALLBACK_INDEX_BLE_GATT_CLIENT 0x07 56 | #define CALLBACK_INDEX_HFP 0x08 57 | 58 | /** 59 | * Registers client info for a specific session handle with the session 60 | * manager. 61 | * Framework clients can store and retrieve this info for use-cases like 62 | * storing callback 63 | * pointers to notify their respective app clients. 64 | * 65 | * @param sessionHandle session handle for current Bluetooth session 66 | * @param p_client_data pointer to data 67 | * @param : dataIndex , index of this data within senssion 68 | * @return ACEBT_STATUS_SUCCESS if registered successfully; else, 69 | * aceBT_status_t error code 70 | */ 71 | status_t registerBTClientData(sessionHandle sessionHandle, uint8_t dataIndex, void* p_client_data); 72 | 73 | typedef enum { 74 | /** Common APIs for Classic and BLE */ 75 | ACE_BT_API_SESSION_OPEN = 0, 76 | ACE_BT_API_SESSION_CLOSE, 77 | ACE_BT_API_REGISTER, 78 | ACE_BT_API_UNREGISTER, 79 | ACE_BT_API_ENABLE, 80 | ACE_BT_API_DISABLE, 81 | ACE_BT_API_ENABLE_HCI, 82 | ACE_BT_API_DISABLE_HCI, 83 | ACE_BT_API_GET_ADAPTER_NAME, 84 | ACE_BT_API_SET_ADAPTER_NAME, 85 | ACE_BT_API_GET_ADAPTER_PROPERTIES, 86 | ACE_BT_API_GET_RADIO_STATE, 87 | ACE_BT_API_PAIR_DEVICE, 88 | ACE_BT_API_UNPAIR_DEVICE, 89 | ACE_BT_API_CANCEL_PAIR, 90 | ACE_BT_API_SET_PIN_REPLY, 91 | ACE_BT_API_SET_SSP_REPLY, 92 | ACE_BT_API_GET_NAME, 93 | ACE_BT_API_GET_DEVICE_TYPE, 94 | ACE_BT_API_GET_BONDED_DEVICES_COUNT, 95 | ACE_BT_API_GET_BONDED_DEVICES, 96 | ACE_BT_API_GET_CONNECTED_DEVICES_COUNT, 97 | ACE_BT_API_GET_CONNECTED_DEVICES, 98 | ACE_BT_API_GET_BOND_STATE, 99 | ACE_BT_API_GET_CONNECTION_STATE, 100 | ACE_BT_API_READ_RSSI, 101 | ACE_BT_API_COMMON_MAX = 100, 102 | /** APIs for Classic device management */ 103 | ACE_BT_API_START_SCAN = 101, 104 | ACE_BT_API_STOP_SCAN = 102, 105 | ACE_BT_API_GET_SCAN_MODE = 103, 106 | ACE_BT_API_SET_SCAN_MODE = 104, 107 | ACE_BT_API_GET_SCAN_TYPE = 105, 108 | ACE_BT_API_SET_SCAN_TYPE = 106, 109 | ACE_BT_API_GET_DEVICE_UUIDS = 107, 110 | ACE_BT_API_GET_DEVICE_UUIDS_COUNT = 108, 111 | ACE_BT_API_GET_EIR = 109, 112 | ACE_BT_API_SET_RESTRICTED_PROFILES = 110, 113 | ACE_BT_API_GET_REMOTE_CLASSIC_PROPERTY = 111, 114 | ACE_BT_API_CLASSIC_DM_MAX = 200, 115 | /** APIs for classic profiles */ 116 | ACE_BT_A2DP_SRC_API_INIT, 117 | ACE_BT_A2DP_SRC_API_CONNECT, 118 | ACE_BT_A2DP_SRC_API_DISCONNECT, 119 | ACE_BT_A2DP_SRC_API_GET_CONN_STATE, 120 | ACE_BT_A2DP_SRC_API_GET_AUDIO_STATE, 121 | ACE_BT_A2DP_SRC_MAX = 300, 122 | ACE_BT_A2DP_SNK_API_INIT, 123 | ACE_BT_A2DP_SNK_API_CONNECT, 124 | ACE_BT_A2DP_SNK_API_DISCONNECT, 125 | ACE_BT_A2DP_SNK_API_GET_CONN_STATE, 126 | ACE_BT_A2DP_SNK_API_GET_AUDIO_STATE, 127 | ACE_BT_AVRC_CTRL_INIT, 128 | ACE_BT_AVRC_CTRL_REGISTER, 129 | ACE_BT_AVRC_CTRL_UNREGISTER, 130 | ACE_BT_AVRC_CTRL_SEND_KEY, 131 | ACE_BT_A2DP_SNK_MAX = 400, 132 | ACE_BT_HID_HOST_API_INIT, 133 | ACE_BT_HID_HOST_API_CONNECT, 134 | ACE_BT_HID_HOST_API_DISCONNECT, 135 | ACE_BT_HID_HOST_API_GET_PROFILE_STATE, 136 | ACE_BT_HID_HOST_API_VIRTUAL_UNPLUG, 137 | ACE_BT_HID_HOST_API_SEND_DATA, 138 | ACE_BT_HID_HOST_API_GET_BATTERY_LEVEL, 139 | ACE_BT_HID_HOST_API_GET_PROTOCOL, 140 | ACE_BT_HID_HOST_API_SET_PROTOCOL, 141 | ACE_BT_HID_HOST_API_GET_REPORT, 142 | ACE_BT_HID_HOST_API_SET_REPORT, 143 | ACE_BT_HID_HOST_MAX = 500, 144 | ACE_BT_HFP_API_INIT, 145 | ACE_BT_HFP_API_CONNECT, 146 | ACE_BT_HFP_API_DISCONNECT, 147 | ACE_BT_HFP_API_REGISTER, 148 | ACE_BT_HFP_API_GET_PROFILE_STATE, 149 | ACE_BT_HFP_API_GET_AUDIO_STATE, 150 | ACE_BT_HFP_API_CONNECT_AUDIO, 151 | ACE_BT_HFP_API_DISCONNECT_AUDIO, 152 | ACE_BT_HFP_API_CHANGE_CALL_STATE, 153 | ACE_BT_HFP_MAX = 600, 154 | /** APIs for BLE */ 155 | ACE_BT_BLE_REGISTER_BEACON_CLI_API, 156 | ACE_BT_BLE_UNREGISTER_BEACON_CLI_API, 157 | ACE_BT_BLE_GET_MAX_ADV_DATA_LEN_API, 158 | ACE_BT_BLE_START_BEACON_API, 159 | ACE_BT_BLE_START_BEACON_WITH_SCAN_RSP_API, 160 | ACE_BT_BLE_SET_PERIODIC_BEACON_PARAM_API, 161 | ACE_BT_BLE_STOP_BEACON_API, 162 | ACE_BT_BLE_START_BEACON_SCAN_API, 163 | ACE_BT_BLE_STOP_BEACON_SCAN_API, 164 | ACE_BT_BLE_API_MAX = 700, 165 | /*GATT SERVER */ 166 | ACE_BT_BLE_REGISTER_GATT_SERVER_API, 167 | ACE_BT_BLE_UNREGISTER_GATT_SERVER_API, 168 | ACE_BT_BLE_GATT_ADD_SERVICE_API, 169 | ACE_BT_BLE_GATT_SEND_RESPONSE_API, 170 | ACE_BT_BLE_GATT_SET_MTU_API, 171 | ACE_BT_BLE_GATT_NOTIFY_CHARACTERISTICS_API, 172 | ACE_BT_BLE_GATT_REMOVE_SERVICE_API, 173 | ACE_BT_BLE_GATT_GET_BDADDR_API, 174 | ACE_BT_BLE_GATT_SERVER_API_MAX = 800, 175 | /* BLE API */ 176 | ACE_BT_BLE_REGISTER_BLE_API = 801, 177 | ACE_BT_BLE_UNREGISTER_BLE_API = 802, 178 | ACE_BT_BLE_GET_MTU_API = 803, 179 | ACE_BT_BLE_SET_MTU_API = 804, 180 | ACE_BT_BLE_CONN_PARAM = 805, 181 | ACE_BT_BLE_CONN_REQ = 806, 182 | ACE_BT_BLE_CANCEL_CONN_REQ = 807, 183 | ACE_BT_BLE_DISC_REQ = 808, 184 | ACE_BT_BLE_READ_RSSI = 809, 185 | ACE_BT_BLE_CONN_PRIORITY = 810, 186 | 187 | ACE_BT_BLE_API_ADAPTER_MAX = 900, 188 | /** APIs for Sockets: Putting under classic for now for the purposes of SPP. 189 | Might move to common if/when we do BLE sockets*/ 190 | ACE_BT_SOCKET_REGISTER_SERVER_CALLBACKS, 191 | ACE_BT_SOCKET_CREATE_SERVER_SOCKET, 192 | ACE_BT_SOCKET_ACCEPT_CLIENT_CONNECTION, 193 | ACE_BT_SOCKET_REGISTER_CLIENT_CALLBACKS, 194 | ACE_BT_SOCKET_CONNECT, 195 | ACE_BT_SOCKET_WRITE, 196 | ACE_BT_SOCKET_READ, 197 | ACE_BT_SOCKET_CLOSE, 198 | ACE_BT_SOCKET_ACL, 199 | ACE_BT_SOCKET_UP, 200 | ACE_BT_SOCKET_GET_LIST, 201 | ACE_BT_SOCKET_API_MAX = 1000, 202 | /*GATT Client */ 203 | ACE_BT_BLE_REGISTER_GATT_CLIENT_API, 204 | ACE_BT_BLE_UNREGISTER_GATT_CLIENT_API, 205 | ACE_BT_BLE_GATT_CLIENT_DISC_ALL_SVC_API, 206 | ACE_BT_BLE_GATT_CLIENT_DISC_PRIMARY_SVC_API, 207 | ACE_BT_BLE_GATT_CLIENT_GET_SERVICE_API, 208 | ACE_BT_BLE_GATT_CLIENT_GET_SERVICE_BYUUID_API, 209 | ACE_BT_BLE_GATT_CLIENT_READ_CHARS_API, 210 | ACE_BT_BLE_GATT_CLIENT_WRITE_CHARS_API, 211 | ACE_BT_BLE_GATT_CLIENT_READ_DESC_API, 212 | ACE_BT_BLE_GATT_CLIENT_WRITE_DESC_API, 213 | ACE_BT_BLE_GATT_CLIENT_SET_NOTIFY_API, 214 | ACE_BT_BLE_GATT_CLIENT_BEGIN_RELIABLE_WRITE_API, 215 | ACE_BT_BLE_GATT_CLIENT_EXEC_RELIABLE_WRITE_API, 216 | ACE_BT_BLE_GATT_CLIENT_ATT_API, 217 | ACE_BT_BLE_GATT_CLIENT_REFRESH_DB_API, 218 | ACE_BT_BLE_GATT_CLIENT_API_MAX = 1100, 219 | /*BTPM testing*/ 220 | ACE_BT_PM_GET_ACL_CONN_LIST_API = 1101, 221 | ACE_BT_PM_GET_CONN_LST_API = 1102, 222 | ACE_BT_PM_GET_AVAIL_CONN_CNT_API = 1103, 223 | ACE_BT_PM_API_REGISTER = 1104, 224 | ACE_BT_PM_SET_MAX_LINK_COUNT = 1105, 225 | ACE_BT_PM_API_MAX = 1200, 226 | /* AVRCP TG */ 227 | ACE_BT_AVRCP_TG_INIT = 1201, 228 | ACE_BT_AVRCP_TG_REGISTER = 1202, 229 | ACE_BT_AVRCP_TG_SEND_METADATA = 1203, 230 | ACE_BT_AVRCP_TG_SEND_PLAY_STATUS = 1204, 231 | ACE_BT_AVRCP_TG_SEND_NOTIF = 1205, 232 | ACE_BT_AVRCP_TG_MAX = 1300, 233 | /* AVRCP CT */ 234 | ACE_BT_AVRCP_CT_REGISTER = 1301, 235 | ACE_BT_AVRCP_CT_SEND_APP_SETTING = 1302, 236 | ACE_BT_AVRCP_CT_MAX = 1400, 237 | 238 | MAX_API = 0xFFFF, /* END of all API's */ 239 | } acebt_ipc_api_enum_t; 240 | 241 | #define ACEBT_BT_CBACK_IDX_COMMON 0x0 242 | #define ACEBT_BT_CBACK_IDX_CLASSIC 0x1 243 | #define ACEBT_BT_CBACK_IDX_HFP 0x2 244 | #define ACEBT_BT_CBACK_IDX_SOCKET_CLIENT 0x3 245 | #define ACEBT_BT_CBACK_IDX_SOCKET_SERVER 0x4 246 | #define ACEBT_BT_CBACK_IDX_BLE 0x5 247 | #define ACEBT_BT_CBACK_IDX_BEACON 0x6 248 | #define ACEBT_BT_CBACK_IDX_GATT_SERVER 0x7 249 | #define ACEBT_BT_CBACK_IDX_GATT_CLIENT 0x8 250 | #define ACEBT_BT_CBACK_IDX_PM 0x9 251 | #define ACEBT_BT_CBACK_IDX_AVRCP_TG 0xA 252 | #define ACEBT_BT_CBACK_IDX_AVRCP_CT 0xB 253 | #define ACEBT_BT_CBACK_IDX_MAX MAX_BT_EVT_HANDLERS 254 | 255 | /* BLE Gatt Client call back mask */ 256 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_REGISTERED 0x01 257 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DISCOVERED 0x02 258 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_READ_CHARS 0x04 259 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_WRITE_CHARS 0x08 260 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_NOTIFY_CHARS 0x10 261 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DESC_WRITE 0x20 262 | #define ACEBT_IPC_CBACK_MASK_GATTC_SERVICE_DESC_READ 0x40 263 | #define ACEBT_IPC_CBACK_MASK_GATTC_GET_DB 0x80 264 | #define ACEBT_IPC_CBACK_MASK_GATTC_EXECUTE_WRITE 0x100 265 | 266 | typedef enum { 267 | /* Common Callback Interfaces under ACEBT_BT_CBACK_IDX_COMMON */ 268 | ACE_BT_CALLBACK_COMMON_INIT = 1, 269 | ACE_BT_CALLBACK_SERVER_RESTARTED = 2, 270 | ACE_BT_CALLBACK_ADAPTER_STATE_CHANGED = 3, 271 | ACE_BT_CALLBACK_BOND_STATE_CHANGED = 4, 272 | ACE_BT_CALLBACK_PIN_REQ_CALLBACK = 5, 273 | ACE_BT_CALLBACK_SSP_REQ_CALLBACK = 6, 274 | ACE_BT_CALLBACK_ACL_CALLBACK = 7, 275 | ACE_BT_CALLBACK_COMMON_MAX = 10, 276 | /* Classic Callback Interfaces under ACEBT_BT_CBACK_IDX_CLASSIC */ 277 | ACE_BT_CALLBACK_CLASSIC_INIT = 11, 278 | ACE_BT_CALLBACK_CONN_STATE_CHANGED = 12, 279 | ACE_BT_CALLBACK_PROFILE_STATE_CHANGED = 13, 280 | ACE_BT_CALLBACK_AUDIO_STATE_CHANGED = 14, 281 | ACE_BT_CALLBACK_DISCOVERY_STATE_CHANGED = 15, 282 | ACE_BT_CALLBACK_DEVICE_FOUND = 16, 283 | ACE_BT_CALLBACK_SCAN_MODE_CHANGED = 17, 284 | ACE_BT_CALLBACK_SCAN_TYPE_CHANGED = 18, 285 | ACE_BT_CALLBACK_ACL_PRIORITY_CHANGED = 19, 286 | ACE_BT_CALLBACK_CLASSIC_MAX = 20, 287 | /* BT HFP Callback Interfaces under ACEBT_BT_CBACK_IDX_HFP */ 288 | ACE_BT_CALLBACK_HFP_INIT = 21, 289 | ACE_BT_CALLBACK_HFP_CONN_STATE = 22, 290 | ACE_BT_CALLBACK_HFP_AUDIO_STATE = 23, 291 | ACE_BT_CALLBACK_HFP_ANSWER = 24, 292 | ACE_BT_CALLBACK_HFP_HANGUP = 25, 293 | ACE_BT_CALLBACK_HFP_DIAL = 26, 294 | ACE_BT_CALLBACK_HFP_CHLD = 27, 295 | ACE_BT_CALLBACK_HFP_CLCC = 28, 296 | ACE_BT_CALLBACK_HFP_BIND = 29, 297 | ACE_BT_CALLBACK_HFP_MAX = 30, 298 | /* BT Socket Client Callback Interfaces under 299 | ACEBT_BT_CBACK_IDX_SOCKET_SERVER */ 300 | ACE_BT_CALLBACK_SOCKET_CLIENT_INIT, 301 | ACE_BT_CALLBACK_SOCKET_CLIENT_CONN, 302 | ACE_BT_CALLBACK_SOCKET_CLIENT_DATA_READY, 303 | ACEBT_BT_CALLBACK_SOCKET_CLIENT_SDP_UUID_FOUND, 304 | ACE_BT_CALLBACK_SOCKET_CLIENT_MAX = 40, 305 | /* BT Socket Server Callback Interfaces under 306 | ACEBT_BT_CBACK_IDX_SOCKET_CLIENT */ 307 | ACE_BT_CALLBACK_SOCKET_SERVER_INIT, 308 | ACE_BT_CALLBACK_SOCKET_SERVER_CONN_REQ, 309 | ACE_BT_CALLBACK_SOCKET_SERVER_CONN_CFM, 310 | ACE_BT_CALLBACK_SOCKET_SERVER_MAX = 50, 311 | /* BLE Callback Interfaces under ACEBT_BT_CBACK_IDX_BLE*/ 312 | ACE_BT_CALLBACK_BLE_INIT, 313 | ACE_BT_CALLBACK_BLE_REGISTERED, 314 | ACE_BT_CALLBACK_BLE_CONN_STATE_CHANGED, 315 | ACE_BT_CALLBACK_BLE_MTU_CHANGED, 316 | ACE_BT_CALLBACK_BLE_READ_RSSI, 317 | ACE_BT_CALLBACK_BLE_MAX = 60, 318 | /* BLE Beacon Manager Callback Interfaces under ACEBT_BT_CBACK_IDX_BEACON */ 319 | ACE_BT_CALLBACK_BEACON_INIT, 320 | ACE_BT_CALLBACK_ADV_STATE_CHANGED, 321 | ACE_BT_CALLBACK_SCAN_STATE_CHANGED, 322 | ACE_BT_CALLBACK_SCAN_RESULT, 323 | ACE_BT_CALLBACK_BEACON_REGISTERED, 324 | ACE_BT_CALLBACK_BEACON_MAX = 70, 325 | /* BLE GATT Server Callback Interfaces under 326 | ACEBT_BT_CBACK_IDX_GATT_SERVER */ 327 | ACE_BT_CALLBACK_GATTS_INIT, 328 | ACE_BT_CALLBACK_GATTS_SERVICE_STARTED, 329 | ACE_BT_CALLBACK_GATTS_CHARS_WRITE_REQ, 330 | ACE_BT_CALLBACK_GATTS_CHARS_READ_REQ, 331 | ACE_BT_CALLBACK_GATTS_DESC_WRITE_REQ, 332 | ACE_BT_CALLBACK_GATTS_DESC_READ_REQ, 333 | ACE_BT_CALLBACK_GATTS_EXEC_WRITE_REQ, 334 | ACE_BT_CALLBACK_GATTS_MTU_CHANGED_REQ, 335 | ACE_BT_CALLBACK_GATTS_NOTIFY_SENT, 336 | ACE_BT_CALLBACK_GATTS_CONN_INDICATION, 337 | ACE_BT_CALLBACK_GATTS_MAX = 80, 338 | 339 | /* BT Policy Manager Callback Interfaces under ACEBT_BT_CBACK_IDX_PM */ 340 | ACE_BT_CALLBACK_PM_CONN_LIST = 81, 341 | ACE_BT_CALLBACK_PM_ACL_LIST = 82, 342 | ACE_BT_CALLBACK_PM_MAX = 90, 343 | 344 | /* BLE GATT Client Callback Interfaces under ACEBT_BT_CBACK_IDX_GATT_CLIENT 345 | */ 346 | ACE_BT_CALLBACK_GATTC_INIT, 347 | ACE_BT_CALLBACK_GATTC_SERVICE_REGISTERED, 348 | ACE_BT_CALLBACK_GATTC_SERVICE_DISCOVERED, 349 | ACE_BT_CALLBACK_GATTC_CHARS_READ_RSP, 350 | ACE_BT_CALLBACK_GATTC_CHARS_WRITE_RSP, 351 | ACE_BT_CALLBACK_GATTC_EXEC_WRITE_RSP, 352 | ACE_BT_CALLBACK_GATTC_NOTIFY_CHARS_CHANGED, 353 | ACE_BT_CALLBACK_GATTC_DESC_WRITE_RSP, 354 | ACE_BT_CALLBACK_GATTC_DESC_READ_RSP, 355 | ACE_BT_CALLBACK_GATTC_GET_DB_RSP, 356 | ACE_BT_CALLBACK_GATTC_MAX = 100, 357 | 358 | /* BT AVRCP Target Callback Interfaces under ACEBT_BT_CBACK_IDX_AVRCP_TG 359 | */ 360 | ACE_BT_CALLBACK_AVRCP_TG_INIT = 101, 361 | ACE_BT_CALLBACK_AVRCP_TG_GET_METADATA = 102, 362 | ACE_BT_CALLBACK_AVRCP_TG_GET_PLAY_STATUS = 103, 363 | ACE_BT_CALLBACK_AVRCP_TG_REG_NOTIFICATION = 104, 364 | ACE_BT_CALLBACK_AVRCP_TG_PASSTHROUGH_CMD = 105, 365 | ACE_BT_CALLBACK_AVRCP_TG_REMOTE_FEATURES = 106, 366 | ACE_BT_CALLBACK_AVRCP_TG_MAX = 110, 367 | 368 | /* BT AVRCP Controller Callback Interfaces under ACEBT_BT_CBACK_IDX_AVRCP_CT 369 | */ 370 | ACE_BT_CALLBACK_AVRCP_CT_INIT = 111, 371 | ACE_BT_CALLBACK_AVRCP_CT_CONN_STATE = 112, 372 | ACE_BT_CALLBACK_AVRCP_CT_REMOTE_FEATURES = 113, 373 | ACE_BT_CALLBACK_AVRCP_CT_SET_APP_SETTING_RSP = 114, 374 | ACE_BT_CALLBACK_AVRCP_CT_APP_SETTING_CHANGED = 115, 375 | ACE_BT_CALLBACK_AVRCP_CT_NOTIF = 116, 376 | ACE_BT_CALLBACK_AVRCP_CT_MAX = 120, 377 | } acebt_ipc_evt_enum_t; 378 | typedef acebt_ipc_evt_enum_t ipc_evt_enum_t; 379 | 380 | typedef struct { 381 | uint32_t size; 382 | uint32_t conn_handle; 383 | uuid_t uuid; 384 | } __attribute__((packed)) acebt_gattc_get_db_data_t; 385 | typedef acebt_gattc_get_db_data_t gattc_get_db_data_t; 386 | 387 | void serialize_ble_get_db_req(gattc_get_db_data_t* data, uint32_t conn_handle, uuid_t* uuid); 388 | 389 | // Reversed struct. Here be dragons 390 | typedef struct { 391 | uint16_t reserved0; // 0x00: always 0x0000? 392 | 393 | uint16_t size; // 0x02: 30 00 → 0x30 (48) 394 | uint32_t msg_type; // 0x04: 02 00 00 00 → 0x02 (2) 395 | 396 | uint32_t function_id; // 0x08: 3e 00 00 00 -> 0x3E (62) 397 | 398 | void* cb1; // 0x0C: 70 44 50 b5 399 | void* cb2; // 0x10: 74 44 50 b5 400 | void* buffer; // 0x14: 48 45 50 b5 401 | 402 | uint32_t reserved1; // 0x18: 00 00 00 00 403 | uint32_t reserved2; // 0x1C: 00 00 00 00 404 | uint32_t reserved3; // 0x20: 00 00 00 00 405 | 406 | uint32_t flags; // 0x24: 01 00 00 00 407 | uint32_t reserved4; // 0x28: 00 00 00 00 408 | 409 | /* Matches aceBT_aipcHandles_t dump at 0x2C–0x2F */ 410 | uint16_t callback_id; // 0x2C: 4c 19 → 0x194C (6476) 411 | uint16_t server_id; // 0x2E: f0 b6 → 0xB6F0 (46832) 412 | } aceAipc_parameter_t; 413 | 414 | /** NOTE: These two functions are used in the BT event handlers. 415 | * getSessionForCallback was used <5.17 416 | * Since >=5.17 they switched to getSessionForTask 417 | * We cannot define them or the linker will fail to resolve 418 | */ 419 | // /** Returns ACE BT Session associated with a callback ID 420 | // * 421 | // * @param callback_client_id Callback ID 422 | // * @return session handle if valid session found else error code 423 | // */ 424 | // sessionHandle getSessionForCallback(uint16_t callback_client_id); 425 | // sessionHandle getSessionForTask(aceAipc_parameter_t* task); 426 | 427 | typedef void (*bt_event_handler)(aceAipc_parameter_t* task); 428 | 429 | /** Registers an AIPC event handler with session manager to receive BT manager 430 | * events 431 | * 432 | * @param sessionHandle session handle for current Bluetooth session 433 | * @return SUCCESS if registration is successful, else error 434 | */ 435 | status_t registerBTEvtHandler( 436 | sessionHandle sessionHandle, bt_event_handler cb, acebt_ipc_evt_enum_t min, 437 | acebt_ipc_evt_enum_t max 438 | ); 439 | 440 | status_t aipc_invoke_sync_call(uint16_t func_id, void* payload, uint32_t len); 441 | 442 | /** 443 | * Retrieve the client callback pointer for a specific session handle. If the 444 | * session handle 445 | * is not found in the session manager, an appropriate error code will be 446 | * returned. 447 | * 448 | * @param sessionHandle session handle for current Bluetooth session 449 | * @param dataIndex index of this data within senssion 450 | * @return ACEBT_STATUS_SUCCESS if sessionHandle is valid; else 451 | * ACEBT_STATUS_INVALID_PARAM 452 | */ 453 | void* getBTClientData(sessionHandle sessionHandle, uint8_t dataIndex); 454 | 455 | /** 456 | * Used for the pre 5.17.0 bleDiscoverAllServices implementation 457 | */ 458 | typedef struct { 459 | uint32_t size; 460 | uint32_t session_handle; 461 | uint32_t conn_handle; 462 | status_t out_status; 463 | } __attribute__((packed)) acebt_request_disc_all_svc_t; 464 | typedef acebt_request_disc_all_svc_t request_disc_all_svc_t; 465 | 466 | void serialize_gattc_disc_all_svc(request_disc_all_svc_t* data, uint32_t conn_handle); 467 | 468 | /** 469 | * Used during BT event handler GATT Client discover all services operations 470 | */ 471 | typedef struct { 472 | uint32_t size; 473 | uint32_t conn_handle; 474 | status_t out_status; 475 | } __attribute__((packed)) acebt_dis_req_t; 476 | typedef acebt_dis_req_t dis_req_t; 477 | 478 | /** 479 | * Used during BT event handler GATT Client get DB operations 480 | */ 481 | typedef struct { 482 | bleGattServiceType_t serviceType; 483 | uuid_t uuid; 484 | } __attribute__((packed)) acebt_included_svc_t; 485 | typedef acebt_included_svc_t included_svc_t; 486 | 487 | typedef enum { 488 | ACEBT_BLE_GATT_CHARACTERISTICS, 489 | ACEBT_BLE_GATT_DESCRIPTOR, 490 | ACEBT_BLE_GATT_INC_SVC, 491 | } aceBT_bleSerealizedGattDataType_t; 492 | typedef aceBT_bleSerealizedGattDataType_t bleSerealizedGattDataType_t; 493 | 494 | typedef struct { 495 | bleSerealizedGattDataType_t dataType; 496 | union { 497 | bleGattCharacteristicsValue_t charValue; 498 | bleGattDescriptor_t descValue; 499 | included_svc_t svc; 500 | }; 501 | } acebt_service_data_t; 502 | typedef acebt_service_data_t service_data_t; 503 | 504 | typedef struct { 505 | uint8_t is_unregister; 506 | uint16_t size; 507 | uint16_t callback_mask; 508 | uint32_t session_handle; 509 | uint32_t conn_handle; 510 | status_t out_status; 511 | bleGattServiceType_t serviceType; 512 | uuid_t uuid; 513 | uint16_t no_svc_data; 514 | service_data_t svc_data[]; 515 | } acebt_register_cback_gatts_t; 516 | typedef acebt_register_cback_gatts_t register_cback_gatts_t; 517 | 518 | typedef struct { 519 | uint16_t no_svc; 520 | uint32_t conn_handle; 521 | register_cback_gatts_t svc_list[]; 522 | } acebt_register_cback_gatts_list_t; 523 | typedef acebt_register_cback_gatts_list_t register_cback_gatts_list_t; 524 | 525 | void deserealize_all_gatts_register_data( 526 | register_cback_gatts_list_t* data, bleGattsService_t** gattService, uint32_t* no_svc 527 | ); 528 | 529 | void cleanup_all_service(bleGattsService_t* service, int no_svc); 530 | 531 | /** 532 | * Used for the pre 5.17.0 bleReadCharacteristics implementation 533 | */ 534 | typedef struct { 535 | uint32_t size; 536 | uint32_t conn_handle; 537 | bleGattCharacteristicsValue_t chars; 538 | status_t status; 539 | } __attribute__((packed)) acebt_gattc_read_chars_req_data_t; 540 | typedef acebt_gattc_read_chars_req_data_t gattc_read_chars_req_data_t; 541 | 542 | void serialize_gattc_read_chars_req( 543 | gattc_read_chars_req_data_t* data, uint32_t conn_handle, 544 | bleGattCharacteristicsValue_t charsValue 545 | ); 546 | 547 | /** 548 | * Used during BT event handler GATT Client read characteristic operations 549 | */ 550 | typedef struct { 551 | uint32_t size; 552 | uint32_t conn_handle; 553 | bleGattCharacteristicsValue_t value; 554 | status_t status; 555 | uint32_t data_len; 556 | uint8_t data[]; 557 | } __attribute__((packed)) acebt_gattc_read_chars_data_t; 558 | typedef acebt_gattc_read_chars_data_t gattc_read_chars_data_t; 559 | 560 | /** 561 | * Used for the pre 5.17.0 bleWriteCharacteristics implementation 562 | */ 563 | void serialize_gattc_write_char_req( 564 | uint32_t conn_handle, bleGattCharacteristicsValue_t* val, uint8_t** out_data, uint32_t* out_len, 565 | responseType_t request_type 566 | ); 567 | 568 | /** 569 | * Used during BT event handler GATT Client write characteristic operations 570 | */ 571 | typedef struct { 572 | uint32_t size; 573 | uint32_t conn_handle; 574 | bleGattCharacteristicsValue_t value; 575 | responseType_t write_type; 576 | status_t status; 577 | uint32_t data_len; 578 | uint8_t data[]; 579 | } __attribute__((packed)) acebt_gattc_write_chars_data_t; 580 | typedef acebt_gattc_write_chars_data_t gattc_write_chars_data_t; 581 | 582 | /** 583 | * Used for the pre 5.17.0 bleWriteDescriptor implementation 584 | */ 585 | void serialize_gattc_write_desc_req( 586 | uint32_t conn_handle, bleGattDescriptor_t* p_desc, uint8_t** out_data, uint32_t* out_len, 587 | responseType_t requestType 588 | ); 589 | 590 | /** 591 | * Used during the BT event handler GATT Client write descriptor operations 592 | */ 593 | typedef struct { 594 | uint32_t size; 595 | uint32_t conn_handle; 596 | bleGattDescriptor_t desc; 597 | responseType_t write_type; 598 | status_t status; 599 | uint32_t data_len; 600 | uint8_t data[]; 601 | } __attribute__((packed)) acebt_gattc_write_desc_data_t; 602 | typedef acebt_gattc_write_desc_data_t gattc_write_desc_data_t; 603 | 604 | /** 605 | * Used for the pre 5.17.0 bleSetNotification implementation 606 | */ 607 | typedef struct { 608 | bool enable; 609 | uint32_t size; 610 | uint32_t conn_handle; 611 | uint32_t session_handle; 612 | bleGattCharacteristicsValue_t chars; 613 | } __attribute__((packed)) acebt_gattc_set_notify_data_t; 614 | typedef acebt_gattc_set_notify_data_t gattc_set_notify_data_t; 615 | 616 | void serialize_ble_set_notify( 617 | gattc_set_notify_data_t* data, uint32_t conn_handle, bleGattCharacteristicsValue_t charsValue, 618 | bool enable 619 | ); 620 | 621 | /** 622 | * Used during BT event handler GATT Client notify characteristic operations 623 | */ 624 | // The schema for this struct has changed. It used to have a `uint8_t confirm` field 625 | // after `session_handle`, and `data_len` used to be `uint16_t`. 626 | typedef struct { 627 | uint32_t size; 628 | uint32_t conn_handle; 629 | uint32_t session_handle; 630 | bleGattCharacteristicsValue_t value; 631 | uint32_t data_len; 632 | uint8_t data[]; 633 | } __attribute__((packed)) acebt_notify_data_t; 634 | typedef acebt_notify_data_t notify_data_t; 635 | 636 | #ifdef __cplusplus 637 | } 638 | #endif 639 | 640 | #endif // COMPAT_ACE_INTERNALS_H 641 | --------------------------------------------------------------------------------